CR8V STACKS — The Blog
Brand Strategy
Positioning your business for long-term recognition and trust.
BRAND
↳ Need this done for you?
BRAND STRATEGY
SERVICES
We build identities that make your business memorable, credible, and easy to trust at first glance.
Positioning & messaging
Visual identity systems
View the Service →
Business Tips
Practical, tested advice for running a growing business.
SHOP
↳ Selling online?
ECOMMERCE
WEBSITES
From Shopify builds to full custom stores — we design and develop ecommerce sites that convert.
Shopify & custom builds
Checkout & payments setup
View the Service →
Content Creation
Writing and formats that keep an audience coming back.
WRITE
↳ Need content, not just tips?
CONTENT WRITING
SERVICES
Blog posts, web copy, and long-form content written to rank and to actually convert readers.
SEO-driven blog writing
Website & landing page copy
View the Service →
Digital Marketing
Strategies to grow reach, traffic, and qualified leads.
GROW
↳ Ready to grow faster?
DIGITAL MARKETING
SERVICES
SEO, email, and social strategy handled end-to-end so your traffic turns into real customers.
SEO & SEM campaigns
Email & social management
View the Service →
Web Design & Development
Building fast, functional sites that look as good as they perform.
BUILD
↳ Time for a new site?
WEBSITE DESIGN
& DEV
WordPress, Shopify, or fully custom — we design and build sites that are built to convert.
WordPress & Shopify builds
Fully custom development
View the Service →
CR8V Stacks
↳ CR8V Stacks Blog
BROWSE BY
CATEGORY
Brand Strategy Business Marketing
9
Business Productivity Systems, tools, and workflows
6
Business Tips Ecommerce · Small Business
25
Career Planning Grow your professional life
5
Content Creation Blogging · Content Writing
17
Digital Marketing Email · SEM · SEO · Social
41
Web Design & Development WordPress · Shopify · Ecommerce
60
Tutorials Step-by-step guides and how-tos
22
↳ Like what you're reading?
LET'S BUILD
SOMETHING TOGETHER
Explore Our Services
Hover Image Reveal List — Elementor Tutorial | Cr8v Stacks
Elementor Tutorial — CSS + JavaScript

Hover Image Reveal List
Editorial Service List

An editorial service list section where hovering over typographic rows reveals floating photos following the cursor. Built with two HTML widgets, no third-party plug-ins required.

Elementor Free 2 HTML widgets Follow Cursor JS Intermediate Mobile tap reveal
Hover the rows below — this is what you will build
01
Web Design
Crafting bespoke digital experiences that captivate and convert.
02
Development
Building fast, robust, and scalable web applications with clean code.
03
Branding
Creating unique, memorable, and enduring visual brand identities.
Live preview — hover to trigger the floating image

Typographic rows are separated by thin lines. On desktop, hovering over a row triggers a floating photo that smoothly tracks the cursor. On mobile, tapping a row reveals the image inline beneath the text. Everything is driven by two HTML widgets and a set of CSS classes assigned 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 templates 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!
01
Structure

Architecture tree

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

Services Section  (Elementor Section)
└── Parent  (single Column inside Section)
      │
      ├── Styles Widget  (HTML Widget) ← FIRST element inside Parent
      │
      ├── Row 1  (Inner Section · Advanced CSS Classes: service-row)
      │     ├── Number (Column)
      │     │     └── Label (Text Widget)
      │     ├── Name (Column)
      │     │     └── Title (Heading Widget · Advanced CSS Classes: service-title)
      │     ├── Description (Column)
      │     │     └── Body (Text Widget · Advanced CSS Classes: service-body)
      │     └── Image (Column · Advanced CSS Classes: service-img-col)
      │           └── Thumbnail (Image Widget · Advanced CSS Classes: service-img)
      │
      ├── Row 2  (Inner Section · Advanced CSS Classes: service-row)
      │     ├── Number (Column)
      │     │     └── Label (Text Widget)
      │     ├── Name (Column)
      │     │     └── Title (Heading Widget · Advanced CSS Classes: service-title)
      │     ├── Description (Column)
      │     │     └── Body (Text Widget · Advanced CSS Classes: service-body)
      │     └── Image (Column · Advanced CSS Classes: service-img-col)
      │           └── Thumbnail (Image Widget · Advanced CSS Classes: service-img)
      │
      ├── Row 3  (Inner Section · Advanced CSS Classes: service-row)
      │     └── Same structure — all rows 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 rows: Duplicate any existing row section. Replace the image assets and text inside the widgets. You do not need to update or assign any new CSS class names.
02
Reference

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.

ElementCSS classApplied to
Every row Inner Sectionservice-rowInner Section Settings
Image Column (inside row)service-img-colColumn Settings
Image widget (Thumbnail)service-imgImage Widget Settings
Title Heading Widgetservice-titleHeading Widget Settings
Body Text Widgetservice-bodyText Editor Widget Settings
03
Build 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 rows and both HTML code widgets must live directly inside this column (the "Parent" column).

No nesting. Rows 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 Row 1 — Create Inner Section

Insert an Inner Section below the Styles Widget and set it to a 4-column layout. Open the Inner Section settings, go to the Advanced tab, and set the CSS Classes field to: service-row

4
Build Row 1 — Add Text Widgets

Add the visible columns inside Row 1:

Number column (e.g. 8% width)
  • Add a Text Editor widget for your numbers (e.g. 01).
Name column (e.g. 28% width)
  • Add a Heading widget.
  • In its Advanced tab, set the CSS Class to: service-title
Description column (e.g. 60% width)
  • Add a Text Editor widget.
  • In its Advanced tab, set the CSS Class to: service-body
5
Build Row 1 — Add Image Column

Add the 4th column inside Row 1 (e.g. 4% width):

Image Column Class
  • Click the column border to edit the column itself.
  • In the Advanced tab, set the CSS Class to: service-img-col
Add Image Widget
  • Drag an Image widget inside this column and select your image from the Media Library.
  • In the Advanced tab, set the CSS Class to: service-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 cursor-following image container.
6
Duplicate for remaining Rows

Duplicate Row 1 to create Row 2 and Row 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 row sections). This is your Script Widget.

04
Code

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) {
    .service-img-col {
      display: none !important;
      width: 0 !important;
      padding: 0 !important;
      margin: 0 !important;
    }
  }

  /* Mobile image column */
  @media (max-width: 768px) {
    .service-img-col {
      display: none !important;
      width: 100% !important;
      padding: 0 !important;
      overflow: hidden;
      max-height: 0;
      transition: max-height 0.4s ease, padding 0.4s ease;
    }

    .service-row--open .service-img-col {
      display: block !important;
      max-height: 300px;
      padding: 16px 0 8px 0 !important;
    }

    .service-img-col img {
      width: 100%;
      height: 200px;
      object-fit: cover;
      border-radius: 6px;
      display: block;
    }
  }

  /* Floating image box (desktop only) */
  .hr-float-img {
    position: fixed;
    top: 0;
    left: 0;
    width: 300px;
    height: 210px;
    pointer-events: none;
    z-index: 99999;
    opacity: 0;
    transform: translate(-50%, -60%) scale(0.88) rotate(-2deg);
    transition: opacity 0.38s cubic-bezier(0.25, 1, 0.5, 1),
                transform 0.38s cubic-bezier(0.25, 1, 0.5, 1);
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18);
  }

  /* Visible state on hover */
  .hr-float-img.hr-visible {
    opacity: 1;
    transform: translate(-50%, -60%) scale(1) rotate(-1.5deg);
  }

  .hr-float-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  /* Hide floating box on mobile entirely */
  @media (max-width: 768px) {
    .hr-float-img {
      display: none !important;
    }
  }

  /* Row hover effects */
  .service-row {
    cursor: pointer;
    transition: padding-left 0.4s cubic-bezier(0.25, 1, 0.5, 1);
  }

  .service-row:hover {
    padding-left: 24px;
  }

  .service-title,
  .service-title * {
    transition: color 0.3s ease;
  }

  .service-row:hover .service-title,
  .service-row:hover .service-title * {
    color: #1a1a1a !important;
  }

  .service-body,
  .service-body * {
    transition: color 0.3s ease;
  }

  .service-row:hover .service-body,
  .service-row:hover .service-body * {
    color: rgba(0, 0, 0, 0.8) !important;
  }

</style>

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

Widget 2 — Script Widget
<!-- WIDGET 2: Script Widget -->
<script>
(function () {

  if (window._hrInitDone) return;
  window._hrInitDone = true;

  function initHoverEffect() {
    var floatImg = document.getElementById('hrFloatImg');

    if (!floatImg) {
      floatImg = document.createElement('div');
      floatImg.id = 'hrFloatImg';
      floatImg.className = 'hr-float-img';

      var inner = document.createElement('img');
      inner.id = 'hrFloatImgEl';
      inner.src = '';
      inner.alt = '';

      floatImg.appendChild(inner);
      document.body.appendChild(floatImg);
    }

    var floatImgEl = document.getElementById('hrFloatImgEl');
    var isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);

    if (!isTouchDevice) {
      var mouseX = 0, mouseY = 0, curX = 0, curY = 0;

      function lerp(a, b, t) { return a + (b - a) * t; }

      function loop() {
        curX = lerp(curX, mouseX, 0.1);
        curY = lerp(curY, mouseY, 0.1);
        floatImg.style.left = curX + 'px';
        floatImg.style.top  = curY + 'px';
        requestAnimationFrame(loop);
      }
      loop();

      document.addEventListener('mousemove', function (e) {
        mouseX = e.clientX;
        mouseY = e.clientY;
      });
    }

    if (!isTouchDevice) {
      document.querySelectorAll('.service-row').forEach(function (row) {
        var imgEl = row.querySelector('.service-img img');
        if (!imgEl) return;

        row.addEventListener('mouseenter', function () {
          floatImgEl.src = imgEl.src;
          floatImg.classList.add('hr-visible');
        });

        row.addEventListener('mouseleave', function () {
          floatImg.classList.remove('hr-visible');
        });
      });
    }

    if (isTouchDevice) {
      document.querySelectorAll('.service-row').forEach(function (row) {
        var imgCol = row.querySelector('.service-img-col');
        if (!imgCol) return;

        row.addEventListener('click', function () {
          var isOpen = row.classList.contains('service-row--open');

          document.querySelectorAll('.service-row--open').forEach(function (r) {
            r.classList.remove('service-row--open');
          });

          if (!isOpen) {
            row.classList.add('service-row--open');
          }
        });
      });
    }
  }

  var isEditor = window.elementorFrontend
    && typeof window.elementorFrontend.isEditMode === 'function'
    && window.elementorFrontend.isEditMode();

  if (isEditor) {
    window.jQuery(window).on('elementor/frontend/init', function() {
      window._hrInitDone = false;
      initHoverEffect();
    });

    setTimeout(function () {
      if (!document.getElementById('hrFloatImg')) {
        initHoverEffect();
      }
    }, 1000);

  } else if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initHoverEffect);

  } else {
    initHoverEffect();
  }

}());
</script>
05
Mobile

Mobile behaviour & touch screens

On touch screens, the floating cursor follow layout is disabled. Instead, tapping a card expands the max-height of the images column to reveal the image stacked vertically inline below the card text content.

Elementor Breakpoint Setup

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

  • Set the Number, Name, and Description columns to 100% width so they stack vertically.
  • Set the Image column to 100% width.

The stylesheet handles hiding/expanding the height on tap, and the script handles the click toggle. Only one card section can remain expanded at any time.

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 emulation.
06
Troubleshooting

Troubleshooting

I can see the outline of three images at rest on desktop

Ensure that the CSS class service-img-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.

Image does not change color or brighten on hover

Verify that your Text and Heading widgets have the classes service-title and service-body applied. The stylesheet forces elements with these classes to transition their colors properly when the parent row is hovered.

The floating image does not follow the cursor

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.

07
Checklist

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, below all rows.
Card sections have the CSS class 'service-row'.
Images column has the CSS class 'service-img-col'.
Image widgets have the CSS class 'service-img'.
08
Concept

How it works — layers and pseudo-elements

The page layout is structured to maintain a clean layout context. Elements are positioned and hidden using standard z-index configurations:

SelectorWhat it does
.service-row Main row container. Triggers transition padding on hover to animate sliding to the right.
.hr-float-img Injected dynamic overlay containing the image. Fixed position with a high z-index that follows cursor movement on desktop using lerp (linear interpolation). Hidden completely on mobile.
.service-img-col Standard image container widget. Collapsed on desktop and expanded dynamically on mobile using max-height animation transitions.

Get your Free eBook

Enter your details below and get instant access to the eBook — and we’ll email it to you too!

Sign Up

Get the latest update on small business marketing and general digital marketing contents.