A Better Woocommerce Single Product Image Gallery
I love Woocommerce. It provides a flexibility in true web development that no other platform, and especially not Shopify, can provide. You can literally do anything that is possible on the web with it.
That said, it’s a plugin and by the nature of plugins, it has to be a “catch all” for every use case right out of the box. Which ends up in a ton of code that slows your site down, at least when you just activate the plugin and leave it at that.
For those of us who want to make the best web we can, however, luckily there are ways we can make Woocommerce even tighter within our own themes.
Today we’ll dive into what I consider a wonderfully lean version of the Woocommerce Single Product Image Gallery, which is basically just where the images we associate with a product are shown. The gallery we’ll create is similar to what Woocommerce puts out by default, but better.
Why is it “better”?
Skip this and get to the code →
- There’s way less code, which is good for speed and the web in general.
- You can pinch and zoom on a mobile device, something you can’t do on some popular sites like Amazon, Home Depot and countless other smaller sites. Pinch and zoom is a default function of devices, so eliminating it in favor of some “zoom” capability seems counterintuitive to the typical user experience.
- It doesn’t have a zoom feature at all. Modern desktops and mobile devices provide ways to do this, so why mess with those in favor of bulky, site-slowing features that require users to “learn” how our website works.
- It doesn’t use a slider. To be fair, neither does Woocommerce out of the box, but sliders have generally been proven to make it less likely our visitors will see all of the content. Everyone thinks they’re awesome but then nobody really uses them, if I can blatantly generalize. But sliders are also bulky when it comes to Pagespeed, too.
- Finally, like Woo’s default set up, it uses a big image and a bunch of thumbnails that you click on to swap the big image out. This is something we’re all familiar with, since it’s how most built-in photo apps work on our mobile devices and computers.
Disagree? The comments section is open, and I’d love to hear your opinion.
The final result will be something like so:

The Code!
First up, we need to replace the default gallery markup with our own. You can do this by overriding the template in your theme. I’d rather not, because then when Woo updates that template, you need to go in and make sure your overrides are all good and update the version number and your client is calling you like, “Dude, bro, sir! The website is the broken and I need the update now when you’re on vacation at night and stuff!”
So instead, we can override it via functions.php to use our own template. Read more about this over at the WordPress StackExchange.
add_filter( 'wc_get_template', 'modify_product_gallery_template', 10, 5 );
function modify_product_gallery_template( $located, $template_name, $args, $template_path, $default_path ) {
if ( 'single-product/product-image.php' == $template_name ) {
$located = get_template_directory().'/assets/inc/single-product-gallery.php';
}
return $located;
}
Note that you’ll need to replace /assets/inc/single-product-gallery.php
with the location of a file you create in your theme, which we’ll do next.
Here’s the code for our single-product-gallery.php file:
<figure class="product-images">
<div class="main-image">
<?php the_post_thumbnail('woocommerce_single'); ?>
</div>
<div class="gallery-images">
<?php global $product;
$attachment_ids = $product->get_gallery_image_ids();
foreach ($attachment_ids as $attachment_id) {
$full_sized_image = wp_get_attachment_image_url($attachment_id, 'large'); ?>
<a href="<?php echo $full_sized_image; ?>">
<?php $attr = array(
'data-full' => $full_sized_image
);
echo wp_get_attachment_image($attachment_id, 'thumbnail', false, $attr); ?>
</a>
<?php } ?>
<a href="<?php echo get_the_post_thumbnail_url(); ?>">
<?php $full_sized_image = get_the_post_thumbnail_url(get_the_ID(), 'full');
$attr = array(
'data-full' => $full_sized_image
);
the_post_thumbnail('thumbnail', $attr ); ?>
</a>
</div>
</figure>
A quick explanation of what we’re doing here, or just skip to the next bit.
Here we just wrap it all in a figure tag, which is appropriate for a bunch of images:
<figure class="product-images">
Next we’ll show the “main image” – basically the large image people will primarily be looking at. At first, it’ll be the image we assign to the Product Image feature in the product editor.
<div class="main-image">
<?php the_post_thumbnail('woocommerce_single'); ?>
</div>
In this next section, we’ll show the thumbnails folks can click on to (eventually) replace the main image.
<div class="gallery-images">
You can research the rest of that code a bit more, but some specific things.
Here we link each thumbnail to it’s full sized image. You could even open this in a new tab, as controversial as such things are! We’ll use jQuery to prevent this from actually happening, but for those folks who disable Javascript and ruin the internet for themselves, this’ll help.
<a href="<?php echo $full_sized_image; ?>">
Again, get into wp_get_attachment_image()
more on your own, but we’re passing this 'data-full' => $full_sized_image
in order to later allow the swapping of the main image with the full-sized version of each thumbnail.
<?php $attr = array(
'data-full' => $full_sized_image
);
echo wp_get_attachment_image($attachment_id, 'thumbnail', false, $attr);
Finally, we’ll add the main image / “Product Image” again so there’s a thumbnail of it. Otherwise, there would be no way to show the original image again.
<a href="<?php echo get_the_post_thumbnail_url(); ?>">
<?php $full_sized_image = get_the_post_thumbnail_url(get_the_ID(), 'full');
$attr = array(
'data-full' => $full_sized_image
);
the_post_thumbnail('thumbnail', $attr ); ?>
</a>
figure.product-images {
width: 100%;
}
figure.product-images img {
display: block;
width: 100%;
height: auto;
margin-bottom: 1rem;
}
.gallery-images {
display: flex;
justify-content: left;
flex-wrap: wrap;
}
.gallery-images a {
display: block;
width: calc(25% - 0.75rem);
margin-right: 1rem;
}
.gallery-images a:nth-of-type(4n + 4) {
margin-right: 0;
}
Feel free to adjust as suits your particular needs, none of this is essential to what we’re doing.
And now for the jQuery. I don’t mind doing this with jQuery because nearly every WordPress site, and even more sites that use Woocommerce, are going to be queuing up jQuery anyway.
You can put this in some .js file you already use, directly into the footer.php file or your site, or inject it by adding an action to wp_footer
. You can also wrap it in a is_product()
conditional to get even more streamlined.
This just swaps our main image out for the full-sized version of our thumbnail, as we click on the thumbnails (note how we’re using our data-full
attribute here.
$('.gallery-images a').click(function(e){
e.preventDefault();
var src = $(this).find('img').data('full');
$('.main-image img').removeAttr('srcset').attr('src', src);
});
And that’s it!