Type to search live articles via WordPress API...
CATEGORY
Brand Strategy

Positioning your business for long-term recognition and trust.

business marketing
Brand Strategy

We build identities that make your business memorable, credible, and easy to trust at first glance.

View Service →
CATEGORY
Business Tips

Practical, tested advice for running a growing business.

Ecommerce hub Small Business Hub
Ecommerce Websites

From Shopify builds to full custom stores — we design and develop ecommerce sites that convert.

View Service →
CATEGORY
Content Creation

Writing and formats that keep an audience coming back.

Blogging Content Writing
Content Writing

Blog posts, web copy, and long-form content written to rank and convert readers into clients.

View Service →
CATEGORY
Web Design

Building fast, functional sites that look as good as they perform.

Ecommerce Website Website Design
Website Design

High-conversion custom websites designed in Figma and hand-coded in native WordPress code.

View Service →
Cr8v Stacks
// CR8V INSIGHTS
Strategy, Design & Code
Back to Blog
Tutorials

Triple Fan Reveal on Hover Elementor Tutorial

Published June 15, 2026 · 20 Min Read · By Mallami Adekunle
AI DEEP SUMMARY






Triple Fan Reveal — Elementor Tutorial | Cr8v Stacks



Elementor Tutorial — CSS + JavaScript

Triple Fan Reveal
on Hover

A card section where hovering reveals three images in a fan. The centre image rises forward; the flanking images tilt outward at opposing angles. Two HTML widgets, no plugins required.
Elementor Free
2 HTML widgets
CSS ::before + ::after
Intermediate
Mobile tap reveal

Three images are hidden inside every card. On desktop, hovering triggers a CSS fan animation. On mobile, tapping reveals the images stacked below the text. Everything is driven by two HTML widgets and a set of CSS classes you assign in Elementor’s Advanced tab.



Template Download: Need the pre-built Elementor container template? Download the .zip archive containing the layout JSON.
Download .zip

How to import the JSON file in WordPress: WordPress security rules prevent uploading raw .json files directly to the Media Library. To work around this:

  1. Download the .zip file above and extract the .json file on your computer.
  2. In WordPress, navigate to Templates → Saved Templates.
  3. Click Import Templates at the top, select your extracted .json file, and click Import Now.
  4. Once imported, simply load the template inside your Elementor canvas by opening the template library folder icon and choosing your saved template!


Contents


01Structure

Architecture tree

This is the Elementor widget hierarchy. Place the Styles Widget first and the Script Widget last within your parent column container.

Fan Section  (Elementor Section)
└── Parent  (single Column inside Section)
      │
      ├── Styles Widget  (HTML Widget) ← FIRST element inside Parent
      │
      ├── Card 1  (Inner Section · Advanced CSS Classes: fan-card)
      │     ├── Text Column  (e.g. 55% width)
      │     │     ├── Tag widget   (Text Widget · Advanced CSS Classes: fan-tag)
      │     │     └── Title widget (Heading Widget · Advanced CSS Classes: fan-title)
      │     └── Images Column  (e.g. 45% width · Advanced CSS Classes on the column: fan-imgs-col)
      │           ├── Left Image   (Image Widget · Advanced CSS Classes: fan-img)
      │           ├── Centre Image (Image Widget · Advanced CSS Classes: fan-img)
      │           └── Right Image  (Image Widget · Advanced CSS Classes: fan-img)
      │
      ├── Card 2  (Inner Section · Advanced CSS Classes: fan-card)
      │     ├── Text Column
      │     │     ├── Tag widget   (Advanced CSS Classes: fan-tag)
      │     │     └── Title widget (Advanced CSS Classes: fan-title)
      │     └── Images Column (Advanced CSS Classes on the column: fan-imgs-col)
      │           ├── Left Image   (Advanced CSS Classes: fan-img)
      │           ├── Centre Image (Advanced CSS Classes: fan-img)
      │           └── Right Image  (Advanced CSS Classes: fan-img)
      │
      ├── Card 3  (Inner Section · Advanced CSS Classes: fan-card)
      │     └── Same structure — all cards use the exact same classes
      │
      └── Script Widget  (HTML Widget) ← LAST element inside Parent

Styles Widget must be first. Script Widget must be last. The CSS must load before the widgets render to properly hide structural containers. The script must load last so it runs after all widgets exist in the DOM.
Adding cards: Duplicate any existing card section. Replace the image assets and text inside the widgets. You do not need to update or assign any new CSS class names.

02Reference

CSS class reference

Assign these classes in the Advanced → CSS Classes tab of each Elementor item. Match the targets precisely for the widgets to be identified by the script.

Element CSS class Applied to
Every card Inner Section fan-card Inner Section Settings
Images Column (inside card) fan-imgs-col Column Settings
All three Image widgets fan-img Image Widget Settings
Tag Text Widget fan-tag Text Editor Widget Settings
Title Heading Widget fan-title Heading Widget Settings


03Build guide

Step-by-step build guide

1
Create the Section and Parent Column
Add a new Section to your page containing a single column. All cards and both HTML code widgets must live directly inside this column (the “Parent” column).
No nesting. Cards must be direct children of the Parent column so the script handles the container structure correctly.
2
Add the Styles Widget (First element)
Drag an HTML widget to the very top of the Parent column. This will act as the Styles Widget. Leave it empty for now; you will add the CSS code in the next section.
3
Build Card 1 — Create Inner Section
Insert an Inner Section below the Styles Widget and set it to a 2-column layout. Open the Inner Section settings, go to the Advanced tab, and set the CSS Classes field to: fan-card
4
Build Card 1 — Add Text Widgets
In the left column of Card 1’s Inner Section:
Tag widget

  • Add a Text Editor widget.
  • In its Advanced tab, set the CSS Class to: fan-tag

Title widget

  • Add a Heading widget.
  • In its Advanced tab, set the CSS Class to: fan-title

5
Build Card 1 — Add Image Widgets
In the right column of Card 1’s Inner Section:
Images Column Class

  • Click the column border to edit the column itself.
  • In the Advanced tab, set the CSS Class to: fan-imgs-col

Add Three Image Widgets

  • Drag three Image widgets into this column. Order is key: the 1st widget acts as the left image, the 2nd as the centre, and the 3rd as the right.
  • For all three Image widgets, set the CSS Class in the Advanced tab to: fan-img

Column is hidden on desktop. Once the CSS is added, this column will disappear on desktop. The script automatically reads the image sources to render the floating fan container.
6
Duplicate for remaining Cards
Duplicate Card 1 to create Card 2 and Card 3. The CSS classes are preserved automatically. Simply swap the images and change the text content.
7
Add the Script Widget (Last element)
Drag a second HTML widget to the very bottom of the Parent column (below all card sections). This is your Script Widget.

04Code

The two code widgets

Paste the corresponding codes into the HTML widgets created in the steps above.

Widget 1 — Styles Widget (paste inside the first HTML widget)

Widget 1 — Styles Widget

<!-- WIDGET 1: Styles Widget -->
<style>
  /* Hide original source images column on desktop */
  @media (min-width: 769px) {
    .fan-imgs-col {
      display: none !important;
      width: 0 !important;
      padding: 0 !important;
      margin: 0 !important;
    }
  }
  /* Card container */
  .fan-card {
    position: relative !important;
    overflow: hidden !important;
    cursor: pointer;
  }
  /* Force text elements to stay on top of the floating images */
  .fan-tag,
  .fan-title {
    position: relative !important;
    z-index: 10 !important;
  }
  /* Dimming card background overlay */
  .fan-card::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    opacity: 0;
    z-index: 1;
    transition: opacity 0.4s ease;
    pointer-events: none;
  }
  .fan-card.fan-active::before,
  .fan-card.fan-open::before {
    opacity: 1;
  }
  /* Dot-grid layout texture */
  .fan-card::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: radial-gradient(
      circle, rgba(255,255,255,0.06) 1px, transparent 1px
    );
    background-size: 20px 20px;
    opacity: 1;
    z-index: 0;
    pointer-events: none;
    transition: opacity 0.4s ease;
  }
  .fan-card.fan-active::after,
  .fan-card.fan-open::after {
    opacity: 0;
  }
  /* Injected fan overlay wrapper */
  .fan-imgs-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    pointer-events: none;
  }
  /* Single image floating wrapper */
  .fan-float-wrapper {
    position: absolute;
    width: 46%;
    height: 68%;
    border-radius: 4px;
    overflow: hidden;
    opacity: 0;
    transition:
      opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
      transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* Diagonal vignette gradient overlay inside image */
  .fan-float-wrapper::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
      155deg,
      rgba(0, 0, 0, 0.05) 0%,
      rgba(0, 0, 0, 0.42) 100%
    );
    pointer-events: none;
    border-radius: inherit;
    z-index: 1;
  }
  .fan-float-wrapper img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 0;
  }
  /* Centre Image - Rest State */
  .fan-float-wrapper.fan-centre {
    z-index: 3;
    transform: translateY(30px) scale(0.88);
  }
  /* Left Image - Rest State */
  .fan-float-wrapper.fan-left {
    z-index: 2;
    left: 8%;
    transform: translateX(55px) translateY(18px) rotate(8deg) scale(0.82);
    transition-delay: 0.05s;
  }
  /* Right Image - Rest State */
  .fan-float-wrapper.fan-right {
    z-index: 2;
    right: 8%;
    transform: translateX(-55px) translateY(18px) rotate(-8deg) scale(0.82);
    transition-delay: 0.05s;
  }
  /* Hover Active / Tap Open Styles */
  .fan-card.fan-active .fan-float-wrapper,
  .fan-card.fan-open .fan-float-wrapper {
    opacity: 1;
  }
  .fan-card.fan-active .fan-centre,
  .fan-card.fan-open .fan-centre {
    transform: translateY(0) scale(1);
  }
  .fan-card.fan-active .fan-left,
  .fan-card.fan-open .fan-left {
    transform: translateX(0) translateY(8px) rotate(-12deg) scale(0.88);
  }
  .fan-card.fan-active .fan-right,
  .fan-card.fan-open .fan-right {
    transform: translateX(0) translateY(8px) rotate(12deg) scale(0.88);
  }
  .fan-tag,
  .fan-tag * {
    transition: color 0.35s ease;
  }
  .fan-card.fan-active .fan-tag,
  .fan-card.fan-open .fan-tag,
  .fan-card.fan-active .fan-tag *,
  .fan-card.fan-open .fan-tag * {
    color: rgba(255, 255, 255, 0.55) !important;
  }
  .fan-title,
  .fan-title * {
    transition: color 0.35s ease;
  }
  .fan-card.fan-active .fan-title,
  .fan-card.fan-open .fan-title,
  .fan-card.fan-active .fan-title *,
  .fan-card.fan-open .fan-title * {
    color: #ffffff !important;
  }
  /* Responsive styling handles layout structure */
  @media (max-width: 768px) {
    /* Hide the original source images column completely on mobile */
    .fan-imgs-col {
      display: none !important;
      width: 0 !important;
      padding: 0 !important;
      margin: 0 !important;
    }
    /* Scale down fanned images slightly on smaller screens */
    .fan-float-wrapper {
      width: 44%;
      height: 60%;
    }
    .fan-float-wrapper.fan-left {
      left: 4%;
    }
    .fan-float-wrapper.fan-right {
      right: 4%;
    }
  }
</style>

Widget 2 — Script Widget (paste inside the second HTML widget)

Widget 2 — Script Widget

<!-- WIDGET 2: Script Widget -->
<script>
(function () {
  if (window._fanInitDone) return;
  window._fanInitDone = true;
  function initFan() {
    var isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
    document.querySelectorAll('.fan-card').forEach(function(card) {
      // Inject the fan overlay container on both desktop and mobile
      if (!card.querySelector('.fan-imgs-overlay')) {
        var imgEls = card.querySelectorAll('.fan-img img');
        if (imgEls.length >= 3) {
          var overlay = document.createElement('div');
          overlay.className = 'fan-imgs-overlay';
          var roles = ['fan-left', 'fan-centre', 'fan-right'];
          imgEls.forEach(function(imgEl, i) {
            var wrapper = document.createElement('div');
            wrapper.className = 'fan-float-wrapper ' + roles[i];
            var img = document.createElement('img');
            img.src = imgEl.src;
            img.alt = imgEl.alt || '';
            img.loading = 'lazy';
            wrapper.appendChild(img);
            overlay.appendChild(wrapper);
          });
          card.appendChild(overlay);
        }
      }
      // Desktop: mouse hover triggers
      if (!isTouch) {
        card.addEventListener('mouseenter', function() {
          card.classList.add('fan-active');
        });
        card.addEventListener('mouseleave', function() {
          card.classList.remove('fan-active');
        });
      }
      if (isTouch) {
        card.addEventListener('click', function(e) {
          var isOpen = card.classList.contains('fan-open');
          document.querySelectorAll('.fan-card.fan-open').forEach(function(c) {
            c.classList.remove('fan-open');
          });
          if (!isOpen) {
            card.classList.add('fan-open');
          }
        });
      }
    });
  }
  var isEditor = window.elementorFrontend
    && typeof window.elementorFrontend.isEditMode === 'function'
    && window.elementorFrontend.isEditMode();
  if (isEditor) {
    window.jQuery(window).on('elementor/frontend/init', function() {
      window._fanInitDone = false;
      initFan();
    });
    setTimeout(function() {
      if (!document.querySelector('.fan-imgs-overlay')) {
        initFan();
      }
    }, 1000);
  } else if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initFan);
  } else {
    initFan();
  }
}());
</script>


05Mobile

Mobile behaviour & touch screens

On touch screens, the gorgeous three-image fan animation is fully preserved. Since hover does not exist on mobile devices, a tap toggle is used instead. Tapping a card dims its background and fans the three images out dynamically, while the text shifts to high-contrast white layered on top for legibility.

Elementor Breakpoint Setup

Switch to the Mobile Breakpoint viewport within the Elementor editor. For each card section:

  • Set the Text column width to 100%.
  • Set the Images column width to 100% (which remains hidden as the template data source).

The stylesheet automatically applies responsive scaling so the fanned images fit smaller viewports without overflowing, and the script handles the click toggle, closing any other active cards when a new one is tapped.
Testing Tap Interactions: Touch simulation within responsive preview settings might not capture JavaScript event triggers reliably. Test on a live mobile browser or using Chrome DevTools Touch Device emulation.

06Troubleshooting

Troubleshooting

I can see the outline of three images at rest on desktop
Ensure that the CSS class fan-imgs-col is applied directly to the Column Settings containing the images, and not inside individual image widgets. The Styles Widget must also load at the very top of your parent column container so it applies initial hiding rules before widgets render.
Fan images overlap the text content when fanned
Verify that your Text and Heading widgets have the classes fan-tag and fan-title applied. The stylesheet forces elements with these classes to use `position: relative` and a higher `z-index: 10` so they sit clearly on top of the fanned images.
The fan effect does not show up at all
Check that the Script Widget is placed as the very last element in the Parent column. Open your browser console (F12) to ensure there are no other JavaScript errors on the page blocking execution.

07Checklist

Pre-publish checklist

Confirm these technical steps before publishing the page:
Checklist 0/5
Styles Widget is the first element inside the Parent column.
Script Widget is the last element inside the Parent column.
Card sections have the CSS class ‘fan-card’.
Images column has the CSS class ‘fan-imgs-col’.
Image widgets have the CSS class ‘fan-img’.

08Concept

How it works — layers and pseudo-elements

The card is a position: relative stacking context. Everything inside it is layered by z-index. The text sits at z-index: 10, always on top. The images live at z-index: 2. Two CSS pseudo-elements handle the styling decoration:

Selector What it does
.fan-card::before Dark dimming overlay at z-index: 1. Invisible at rest (opacity: 0). When JS adds .fan-active to the card, it fades to opacity: 1 — pushing the card background into shadow so the fan images stand out clearly.
.fan-card::after Dot-grid texture at z-index: 0. Visible at rest, giving the dark card surface depth. Fades to opacity: 0 on hover so images appear against a clean background without noise behind them.
.fan-float-wrapper::after Vignette gradient on each individual image. Always visible once images are revealed. This requires a <div> wrapper around each <img> — pseudo-elements cannot attach to <img> tags because images are replaced elements with no children. The Script Widget creates these wrappers automatically.

All fan images start collapsed and hidden at rest. The centre starts scaled down, and the flanking side images start rotated and tucked behind. When the card container receives .fan-active, CSS transitions translate, scale, and rotate them to their open positions.



COMMON QUESTIONS

How much does it cost to work with Cr8v Stacks? +
It depends on scope — a brochure site, an ecommerce store, and a custom AI MVP all price differently. Use our cost calculator for an instant range, or talk to us directly for a full quote.
How long does a typical project take? +
Most website builds run a few weeks from approved design to launch; custom development and AI MVPs vary based on scope. We will give you a real timeline once we understand what you are building.
What services do you actually offer? +
Web design, AI MVP development, and custom development are our core focus — alongside brand strategy, brand identity, and digital marketing.
Do you build on WordPress, Shopify, or something else? +
All of the above, plus WooCommerce and fully custom builds. We recommend the platform based on what your business needs.
Which payment gateways do you integrate? +
This depends on your platform and region — we will confirm exact options (Stripe, Paystack, and others) once we know your setup.
Can you redesign or rebuild a site that already exists? +
Yes — a large share of our work is exactly this. We have rebuilt everything from basic landing pages to full property platforms.
CR8V
DIGITAL AGENCY ECOSYSTEM
READY TO BUILD WHAT YOUR BUSINESS ACTUALLY RUNS ON?
Start Your Discovery Call
© 2026 CR8V STACKS. All rights reserved.
Back to Top