How to Change Image on Product Hover in WooCommerce Products Loop

In this tutorial, you’ll learn how to add interactivity to your WooCommerce stores by showing a second image on product hover inside loops (e.g. archive pages, related products, etc.). This is a feature that many premium WooCommerce themes provide. But, as you’ll see, it’s not that hard to code by ourselves!

This tutorial expects that you are familiar with WordPress and WooCommerce theme development.

Here’s an introductory video that showcases the expected behavior:

Implementation

As usual, for better demonstration, I’ll set up a custom base theme on a local WordPress/WooCommerce installation. I’ll work with their latest versions at the time of this writing (WP 6.4.1/WC 8.3.1).

In the admin, I’ve created three simple products:

WooCommerce productsWooCommerce productsWooCommerce products

Each product has its own image and potentially one or more additional images—all images come from Unsplash:

WooCommerce product imagesWooCommerce product imagesWooCommerce product images

For my design, I want the product images to be square, so I’ll upload images 1000×1000 with the following settings—in your case they might differ:

WooCommerce image sizesWooCommerce image sizesWooCommerce image sizes

Next, I’ll disable WooCommerce default styles—I always do this as I want to have complete control of the styles:

1
//functions.php
2

3
add_filter( 'woocommerce_enqueue_styles', '__return_empty_array' );

Moving on, although completely of secondary importance, I’ll include my beloved UIkit front-end framework to the theme to speed up the development process.

By doing so, my shop page will look like this:

WooCommerce shop pageWooCommerce shop pageWooCommerce shop page

Secondary Image on Hover

This looks ok, but honestly, it isn’t the goal of this tutorial. What I need is a way to change the product image on hover.

Now, many of you might wonder which image will I show. Well, we have lots of options; I can show the first image from the product gallery, a random one from that gallery, or even another image coming from an image field.

In this example, I’ll go with the first option and reveal the first image from the product gallery. To do so, I’ll override the WooCommerce content-product.php file.

To be more specific, after the woocommerce_before_shop_loop_item hook, I’ll add a div with the class of image-wrapper which will wrap anything inside the woocommerce_before_shop_loop_item_title hook.

Overriding the content-product WooCommerce templateOverriding the content-product WooCommerce templateOverriding the content-product WooCommerce template

This code will check if the product image gallery isn’t empty. If that’s the case, it’ll grab the ID of the first image and print its HTML representation using the wp_get_attachment_image() function. Note that the image size will be the woocommerce_thumbnail one that applies to the catalog pages, and earlier I set it to 750px. Also, the image will receive the img-back class for easier targeting through the CSS.

Here’s the required code addition:

1
<?php 
2
/**
3
 * yourtheme/woocommerce/content-product.php
4
 *
5
 * code after the woocommerce_before_shop_loop_item hook
6
 */
7
?>
8

9
<div class="image-wrapper">
10
    <?php 
11
    $product_gallery = $product->get_gallery_image_ids();
12
    
13
    if ( ! empty( $product_gallery ) ) :
14
        echo wp_get_attachment_image(
15
            $product_gallery[0],
16
            'woocommerce_thumbnail',
17
            false,
18
            array(
19
                'class' => 'img-back',
20
            )
21
        );
22
    endif;
23
    
24
    do_action( 'woocommerce_before_shop_loop_item_title' );
25
    ?>
26
</div>

After overriding this WooCommerce template in my theme, my archive pages will look like this:

WooCommerce shop layoutWooCommerce shop layoutWooCommerce shop layout

As you can see, both the main product image (bottom image) and the first product gallery image (top image) appear.

What I miss now is just some styles. Through the CSS, I’ll hide the first image and show it only on hover—depending on your theme, this code might not work without tweaks:

1
.woocommerce-LoopProduct-link {
2
  display: inline-block;
3
}
4

5
.woocommerce-LoopProduct-link .image-wrapper {
6
  position: relative;
7
}
8

9
.woocommerce-LoopProduct-link .img-back {
10
  position: absolute;
11
  top: 0;
12
  left: 0;
13
  width: 100%;
14
  display: none;
15
}
16

17
/*Optional*/
18
.woocommerce-LoopProduct-link:hover {
19
  text-decoration: none;
20
  color: #333;
21
}
22

23
.woocommerce-LoopProduct-link:hover .img-back {
24
  display: block;
25
}

In terms of the output markup, here’s the generated HTML for a product inside the loop:

The generated markup for a product within loopThe generated markup for a product within loopThe generated markup for a product within loop

Another thing to note is that here, all my images have equal dimensions, so I don’t have to use properties like object-fit: cover and height: 100% to position the second image.

Overriding WooCommerce Styles

Bear in mind that above, I described a scenario where I removed WooCommerce default styles and started the styling from scratch.

But in case I wanted to keep the default styles, I’d change some of my selectors to match WooCommerce ones and prevent overrides due to greater CSS specificity like this:

1
.woocommerce ul.products li.product a .img-back {
2
  position: absolute;
3
  top: 0;
4
  left: 0;
5
  width: 100%;
6
  display: none;
7
}
8

9
.woocommerce ul.products li.product a:hover .img-back {
10
  display: block;
11
}

Using Hooks

An alternative implementation, instead of adding the extra code inside the content-product.php file, is to print this code through two custom functions that will run during the woocommerce_before_shop_loop_item_title hook.

An important aspect is to ensure that these functions will run in the correct order. To make this happen, they should have specific priorities following the priorities of the rest of the functions of this hook.

The callback functions of the woocommerce_before_shop_loop_item_title hookThe callback functions of the woocommerce_before_shop_loop_item_title hookThe callback functions of the woocommerce_before_shop_loop_item_title hook

With this in mind, I’ll place this code in the functions.php of my theme:

1
function playground_start_wrapper_div_print_second_product_img() {
2
    global $product;
3
    $product_gallery = $product->get_gallery_image_ids();
4
    echo '<div class="image-wrapper">';
5
    
6
    if ( ! empty( $product_gallery ) ) :
7
        echo wp_get_attachment_image(
8
            $product_gallery[0],
9
            'woocommerce_thumbnail',
10
            false,
11
            array(
12
                'class' => 'img-back',
13
            )
14
        );
15
    endif;
16
}
17

18
function playground_close_wrapper_div_print_second_product_img() {
19
    echo '</div>';
20
}
21

22
add_action( 'woocommerce_before_shop_loop_item_title', 'playground_start_wrapper_div_print_second_product_img', 9 );
23
add_action( 'woocommerce_before_shop_loop_item_title', 'playground_close_wrapper_div_print_second_product_img', 11 );

The styles will remain the same as well as the end result.

Conclusion

During this tutorial, we dived into the WooCommerce e-commerce platform and discussed two ways to reveal a secondary product image upon hovering on the products within loops.

Hopefully, you’ve found this exercise useful enough, and you’ll give it a shot in your upcoming WooCommerce projects. Just remember that depending on your theme setup, especially if you haven’t built it from scratch, you probably have to modify your code to adopt this functionality.

If you want to see more WooCommerce tips and tricks, do let us know through X!

As always, thanks a lot for reading!