Use Case 25 min read

E-commerce Product Image Optimization

Master product image optimization for online stores. Learn techniques for product galleries, zoom functionality, color accuracy, and conversion-focused image strategies.

By ImageGuide Team · Published January 19, 2026 · Updated January 19, 2026
e-commerceproduct imagesconversionzoomgalleriesshopping

Product images directly impact conversions. High-quality, fast-loading product photos can increase sales by 30% or more. This guide covers everything you need to optimize product images for e-commerce success.

Why Product Images Matter

FactorImpact
Image quality75% of shoppers rely on product photos
Multiple anglesIncreases conversion by 20-30%
Zoom capability38% of users zoom on product images
Load speed1s delay reduces conversions by 7%
Mobile experience60%+ of e-commerce traffic

Image Requirements

SpecificationRecommendationReason
Resolution2000×2000 minimumEnables quality zoom
Aspect ratio1:1 (square)Consistent grid layout
FormatAVIF → WebP → JPEGModern + fallback
BackgroundPure white (#FFFFFF)Clean, professional
File sizeUnder 200KB optimizedFast loading

Size Breakdown

Master image: 2400×2400 (archive)
├── Zoom: 1200×1200 (loaded on demand)
├── Main: 800×800 (product page)
├── Thumbnail: 400×400 (gallery)
└── Cart: 150×150 (mini previews)
<div class="product-gallery">
  <!-- Main Image -->
  <div class="main-image">
    <picture>
      <source srcset="product-main.avif" type="image/avif">
      <source srcset="product-main.webp" type="image/webp">
      <img
        src="product-main.jpg"
        alt="Blue Running Shoes - Front View"
        width="800"
        height="800"
        id="mainProductImage"
      >
    </picture>
  </div>

  <!-- Thumbnails -->
  <div class="thumbnails" role="tablist">
    <button
      role="tab"
      aria-selected="true"
      data-full="product-1.jpg"
      data-avif="product-1.avif"
      data-webp="product-1.webp"
    >
      <img src="product-1-thumb.jpg" alt="Front view" width="80" height="80">
    </button>
    <button
      role="tab"
      aria-selected="false"
      data-full="product-2.jpg"
      data-avif="product-2.avif"
      data-webp="product-2.webp"
    >
      <img src="product-2-thumb.jpg" alt="Side view" width="80" height="80">
    </button>
    <button
      role="tab"
      aria-selected="false"
      data-full="product-3.jpg"
      data-avif="product-3.avif"
      data-webp="product-3.webp"
    >
      <img src="product-3-thumb.jpg" alt="Back view" width="80" height="80">
    </button>
  </div>
</div>
class ProductGallery {
  constructor(container) {
    this.container = container;
    this.mainImage = container.querySelector('#mainProductImage');
    this.thumbnails = container.querySelectorAll('[role="tab"]');
    this.supportedFormat = this.detectFormat();

    this.init();
  }

  detectFormat() {
    const canvas = document.createElement('canvas');
    if (canvas.toDataURL('image/avif').indexOf('data:image/avif') === 0) {
      return 'avif';
    }
    if (canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0) {
      return 'webp';
    }
    return 'jpg';
  }

  init() {
    this.thumbnails.forEach(thumb => {
      thumb.addEventListener('click', () => this.switchImage(thumb));
    });

    // Keyboard navigation
    this.container.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        this.navigateByKey(e.key);
      }
    });
  }

  switchImage(thumb) {
    // Update aria states
    this.thumbnails.forEach(t => t.setAttribute('aria-selected', 'false'));
    thumb.setAttribute('aria-selected', 'true');

    // Get appropriate format
    const formatKey = `data-${this.supportedFormat}`;
    const src = thumb.getAttribute(formatKey) || thumb.getAttribute('data-full');

    // Preload then switch
    const img = new Image();
    img.onload = () => {
      this.mainImage.src = src;
    };
    img.src = src;
  }

  navigateByKey(key) {
    const current = this.container.querySelector('[aria-selected="true"]');
    const thumbArray = Array.from(this.thumbnails);
    const currentIndex = thumbArray.indexOf(current);

    let nextIndex;
    if (key === 'ArrowLeft') {
      nextIndex = currentIndex > 0 ? currentIndex - 1 : thumbArray.length - 1;
    } else {
      nextIndex = currentIndex < thumbArray.length - 1 ? currentIndex + 1 : 0;
    }

    this.switchImage(thumbArray[nextIndex]);
    thumbArray[nextIndex].focus();
  }
}

// Initialize
document.querySelectorAll('.product-gallery').forEach(gallery => {
  new ProductGallery(gallery);
});

Zoom Implementation

CSS Hover Zoom

Simple zoom without additional images:

.product-zoom-container {
  position: relative;
  overflow: hidden;
  cursor: zoom-in;
}

.product-zoom-container img {
  transition: transform 0.3s ease;
  transform-origin: center center;
}

.product-zoom-container:hover img {
  transform: scale(1.5);
}

/* Follow cursor position */
.product-zoom-container.active img {
  cursor: zoom-out;
}

Advanced Magnifier Zoom

class ProductZoom {
  constructor(container, options = {}) {
    this.container = container;
    this.image = container.querySelector('img');
    this.options = {
      zoomLevel: 2.5,
      lensSize: 150,
      ...options
    };

    this.zoomImage = null;
    this.lens = null;
    this.init();
  }

  init() {
    this.createLens();
    this.loadZoomImage();

    this.container.addEventListener('mouseenter', () => this.showLens());
    this.container.addEventListener('mouseleave', () => this.hideLens());
    this.container.addEventListener('mousemove', (e) => this.moveLens(e));

    // Touch support
    this.container.addEventListener('touchstart', (e) => this.handleTouch(e));
    this.container.addEventListener('touchmove', (e) => this.handleTouch(e));
    this.container.addEventListener('touchend', () => this.hideLens());
  }

  createLens() {
    this.lens = document.createElement('div');
    this.lens.className = 'zoom-lens';
    this.lens.style.cssText = `
      position: absolute;
      width: ${this.options.lensSize}px;
      height: ${this.options.lensSize}px;
      border: 2px solid #333;
      border-radius: 50%;
      background-repeat: no-repeat;
      pointer-events: none;
      opacity: 0;
      transition: opacity 0.2s;
      box-shadow: 0 2px 10px rgba(0,0,0,0.2);
    `;
    this.container.appendChild(this.lens);
  }

  loadZoomImage() {
    const zoomSrc = this.image.dataset.zoom || this.image.src.replace('-800', '-1600');
    this.zoomImage = new Image();
    this.zoomImage.src = zoomSrc;
  }

  showLens() {
    this.lens.style.opacity = '1';
  }

  hideLens() {
    this.lens.style.opacity = '0';
  }

  moveLens(e) {
    const rect = this.container.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // Position lens
    const lensX = x - this.options.lensSize / 2;
    const lensY = y - this.options.lensSize / 2;
    this.lens.style.left = `${lensX}px`;
    this.lens.style.top = `${lensY}px`;

    // Calculate zoom background position
    const zoomRatio = this.zoomImage.width / this.image.width;
    const bgX = -(x * zoomRatio - this.options.lensSize / 2);
    const bgY = -(y * zoomRatio - this.options.lensSize / 2);

    this.lens.style.backgroundImage = `url(${this.zoomImage.src})`;
    this.lens.style.backgroundSize = `${this.image.width * zoomRatio}px ${this.image.height * zoomRatio}px`;
    this.lens.style.backgroundPosition = `${bgX}px ${bgY}px`;
  }

  handleTouch(e) {
    e.preventDefault();
    const touch = e.touches[0];
    this.showLens();
    this.moveLens(touch);
  }
}

// Initialize
document.querySelectorAll('.product-image-zoom').forEach(container => {
  new ProductZoom(container);
});

For full-screen zoom experience:

class ProductLightbox {
  constructor() {
    this.overlay = null;
    this.currentIndex = 0;
    this.images = [];
    this.init();
  }

  init() {
    this.createOverlay();

    document.querySelectorAll('[data-lightbox]').forEach((img, index) => {
      this.images.push({
        src: img.dataset.zoom || img.src,
        alt: img.alt
      });

      img.addEventListener('click', () => this.open(index));
    });

    // Keyboard navigation
    document.addEventListener('keydown', (e) => {
      if (!this.overlay.classList.contains('active')) return;

      if (e.key === 'Escape') this.close();
      if (e.key === 'ArrowLeft') this.prev();
      if (e.key === 'ArrowRight') this.next();
    });
  }

  createOverlay() {
    this.overlay = document.createElement('div');
    this.overlay.className = 'lightbox-overlay';

    // Close button
    const closeBtn = document.createElement('button');
    closeBtn.className = 'lightbox-close';
    closeBtn.setAttribute('aria-label', 'Close');
    closeBtn.textContent = '\u00d7';
    closeBtn.addEventListener('click', () => this.close());

    // Prev button
    const prevBtn = document.createElement('button');
    prevBtn.className = 'lightbox-prev';
    prevBtn.setAttribute('aria-label', 'Previous');
    prevBtn.textContent = '\u2190';
    prevBtn.addEventListener('click', () => this.prev());

    // Next button
    const nextBtn = document.createElement('button');
    nextBtn.className = 'lightbox-next';
    nextBtn.setAttribute('aria-label', 'Next');
    nextBtn.textContent = '\u2192';
    nextBtn.addEventListener('click', () => this.next());

    // Content area
    const content = document.createElement('div');
    content.className = 'lightbox-content';

    const img = document.createElement('img');
    img.className = 'lightbox-image';
    img.src = '';
    img.alt = '';
    content.appendChild(img);

    // Counter
    const counter = document.createElement('div');
    counter.className = 'lightbox-counter';

    this.overlay.appendChild(closeBtn);
    this.overlay.appendChild(prevBtn);
    this.overlay.appendChild(nextBtn);
    this.overlay.appendChild(content);
    this.overlay.appendChild(counter);

    this.overlay.addEventListener('click', (e) => {
      if (e.target === this.overlay) this.close();
    });

    document.body.appendChild(this.overlay);
  }

  open(index) {
    this.currentIndex = index;
    this.updateImage();
    this.overlay.classList.add('active');
    document.body.style.overflow = 'hidden';
  }

  close() {
    this.overlay.classList.remove('active');
    document.body.style.overflow = '';
  }

  prev() {
    this.currentIndex = this.currentIndex > 0 ? this.currentIndex - 1 : this.images.length - 1;
    this.updateImage();
  }

  next() {
    this.currentIndex = this.currentIndex < this.images.length - 1 ? this.currentIndex + 1 : 0;
    this.updateImage();
  }

  updateImage() {
    const img = this.overlay.querySelector('.lightbox-image');
    const image = this.images[this.currentIndex];
    img.src = image.src;
    img.alt = image.alt;
    this.overlay.querySelector('.lightbox-counter').textContent =
      `${this.currentIndex + 1} / ${this.images.length}`;
  }
}

new ProductLightbox();

Ready-Made Solution: Sirv Media Viewer

Building galleries, zoom, and 360 spin from scratch is complex. Sirv Media Viewer is a production-ready solution that handles all of this out of the box.

Features

FeatureDescription
Image zoomHover, click, or fullscreen zoom with high-res loading
360 spinInteractive product spin from multiple angles
VideoEmbed product videos alongside images
ResponsiveAutomatically adapts to any screen size
Lazy loadingBuilt-in performance optimization
ThumbnailsCustomizable navigation with multiple layouts
FullscreenImmersive viewing with keyboard navigation

Quick Implementation

<!-- Include Sirv JS -->
<script src="https://scripts.sirv.com/sirvjs/v3/sirv.js"></script>

<!-- Product gallery with zoom -->
<div class="Sirv" data-options="fullscreen:true; thumbnails:bottom">
  <div data-src="https://demo.sirv.com/shoes/shoes-1.jpg" data-type="zoom"></div>
  <div data-src="https://demo.sirv.com/shoes/shoes-2.jpg" data-type="zoom"></div>
  <div data-src="https://demo.sirv.com/shoes/shoes-3.jpg" data-type="zoom"></div>
</div>

360 Product Spin

<!-- 360 spin viewer -->
<div class="Sirv" data-src="https://demo.sirv.com/example.spin"></div>
<div class="Sirv" data-options="thumbnails:bottom;">
  <!-- 360 spin as first item -->
  <div data-src="https://your-account.sirv.com/products/shoe.spin"></div>
  <!-- Zoomable images -->
  <div data-src="https://your-account.sirv.com/products/shoe-front.jpg" data-type="zoom"></div>
  <div data-src="https://your-account.sirv.com/products/shoe-side.jpg" data-type="zoom"></div>
  <div data-src="https://your-account.sirv.com/products/shoe-detail.jpg" data-type="zoom"></div>
  <!-- Product video -->
  <div data-src="https://your-account.sirv.com/products/shoe-video.mp4"></div>
</div>

This eliminates hundreds of lines of custom JavaScript while providing a battle-tested, accessible, and performant product viewing experience used by thousands of e-commerce stores.

Learn more about Sirv Media Viewer →

Color Accuracy

Accurate color representation reduces returns.

Maintaining Color Fidelity

# Preserve color profile during conversion
convert input.jpg -profile sRGB.icc -quality 85 output.jpg

# With Sharp
sharp input.jpg --withMetadata --quality 85 output.jpg

Sharp Configuration

const sharp = require('sharp');

async function processProductImage(input, output) {
  await sharp(input)
    // Keep color profile
    .withMetadata({ icc: true })
    // Consistent color space
    .toColorspace('srgb')
    // High quality for products
    .jpeg({
      quality: 85,
      chromaSubsampling: '4:4:4' // Full color detail
    })
    .toFile(output);
}

Color-Accurate Variants

For products where color matters (clothing, paint, makeup):

<div class="color-variants">
  <fieldset>
    <legend>Select Color</legend>
    <label>
      <input
        type="radio"
        name="color"
        value="navy"
        data-image="product-navy.jpg"
        checked
      >
      <span class="swatch" style="background: #1a237e;"></span>
      Navy Blue
    </label>
    <label>
      <input
        type="radio"
        name="color"
        value="burgundy"
        data-image="product-burgundy.jpg"
      >
      <span class="swatch" style="background: #7b1fa2;"></span>
      Burgundy
    </label>
  </fieldset>
</div>

Lazy Loading Strategy

Product Listings

<!-- First row: eager load -->
<div class="product-grid">
  <article class="product-card">
    <img
      src="product-1.jpg"
      alt="Product 1"
      width="400"
      height="400"
      fetchpriority="high"
    >
  </article>
  <article class="product-card">
    <img
      src="product-2.jpg"
      alt="Product 2"
      width="400"
      height="400"
    >
  </article>

  <!-- Below fold: lazy load -->
  <article class="product-card">
    <img
      src="product-3.jpg"
      alt="Product 3"
      width="400"
      height="400"
      loading="lazy"
    >
  </article>
</div>

Thumbnail Preloading

// Preload next/prev images in gallery
function preloadAdjacentImages(currentIndex, images) {
  const toPreload = [
    images[currentIndex - 1],
    images[currentIndex + 1]
  ].filter(Boolean);

  toPreload.forEach(src => {
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = src;
    link.as = 'image';
    document.head.appendChild(link);
  });
}

CDN Configuration

Sirv for E-commerce

// Sirv product image loader
function sirvProductUrl(path, options = {}) {
  const {
    width = 800,
    quality = 85,
    format = 'optimal',
    crop = 'pad',
    background = 'white'
  } = options;

  const params = new URLSearchParams({
    w: width,
    q: quality,
    format,
    'canvas.width': width,
    'canvas.height': width,
    'canvas.color': background,
    'canvas.position': 'center'
  });

  return `https://your-account.sirv.com${path}?${params}`;
}

// Usage
const mainImage = sirvProductUrl('/products/shoe-001.jpg', { width: 800 });
const thumbnail = sirvProductUrl('/products/shoe-001.jpg', { width: 150 });
const zoom = sirvProductUrl('/products/shoe-001.jpg', { width: 1600, quality: 90 });

Cloudinary Configuration

function cloudinaryProductUrl(publicId, options = {}) {
  const {
    width = 800,
    quality = 'auto:best',
    format = 'auto',
    crop = 'pad',
    background = 'white'
  } = options;

  const transforms = [
    `w_${width}`,
    `q_${quality}`,
    `f_${format}`,
    `c_${crop}`,
    `b_${background}`
  ].join(',');

  return `https://res.cloudinary.com/your-cloud/image/upload/${transforms}/${publicId}`;
}

Responsive Product Images

Product Page

<picture class="product-main-image">
  <!-- Large desktop -->
  <source
    media="(min-width: 1200px)"
    srcset="
      product-1200.avif 1200w,
      product-1600.avif 1600w
    "
    type="image/avif"
  >
  <source
    media="(min-width: 1200px)"
    srcset="
      product-1200.webp 1200w,
      product-1600.webp 1600w
    "
    type="image/webp"
  >

  <!-- Tablet -->
  <source
    media="(min-width: 768px)"
    srcset="
      product-600.avif 600w,
      product-800.avif 800w
    "
    type="image/avif"
  >

  <!-- Mobile -->
  <source
    srcset="
      product-400.avif 400w,
      product-600.avif 600w
    "
    type="image/avif"
  >

  <img
    src="product-800.jpg"
    srcset="
      product-400.jpg 400w,
      product-600.jpg 600w,
      product-800.jpg 800w,
      product-1200.jpg 1200w
    "
    sizes="(min-width: 1200px) 50vw, (min-width: 768px) 70vw, 100vw"
    alt="Blue Running Shoes - Premium comfort design"
    width="800"
    height="800"
    loading="eager"
  >
</picture>

Category Listings

<img
  src="product-thumb-400.jpg"
  srcset="
    product-thumb-200.jpg 200w,
    product-thumb-300.jpg 300w,
    product-thumb-400.jpg 400w,
    product-thumb-600.jpg 600w
  "
  sizes="(min-width: 1200px) 20vw, (min-width: 768px) 25vw, 50vw"
  alt="Product Name"
  width="400"
  height="400"
  loading="lazy"
>

Background Removal

Consistent white backgrounds improve visual cohesion.

Sirv AI Studio

Sirv AI Studio provides instant background removal and AI-powered image editing directly in your browser:

  • One-click background removal - Remove backgrounds instantly with AI
  • Background replacement - Add solid colors, gradients, or custom images
  • Batch processing - Process multiple product images at once
  • Direct CDN integration - Edited images are immediately available via Sirv CDN

This eliminates the need for Photoshop or API integrations for most product image workflows.

Try Sirv AI Studio →

Sirv Studio API

For automated pipelines, Sirv Studio API provides programmatic access to all AI features including background removal, upscaling, and product lifestyle shots.

const SIRV_STUDIO_API = 'https://www.sirv.studio/api/zapier';

// Remove background from product image
async function removeBackground(imageUrl) {
  const response = await fetch(`${SIRV_STUDIO_API}/remove-bg`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SIRV_STUDIO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image_url: imageUrl,
      model: 'birefnet' // 1 credit, or 'bria' for 2 credits
    })
  });
  return response.json();
}

// Place product in lifestyle scene
async function createLifestyleShot(productImageUrl, sceneDescription) {
  const response = await fetch(`${SIRV_STUDIO_API}/product-lifestyle`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SIRV_STUDIO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image_url: productImageUrl,
      scene_description: sceneDescription,
      position: 'center_horizontal'
    })
  });
  return response.json();
}

// Batch remove backgrounds (async processing)
async function batchRemoveBackgrounds(images) {
  const response = await fetch(`${SIRV_STUDIO_API}/batch/remove-bg`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SIRV_STUDIO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      images: images.map((url, i) => ({ id: `product-${i}`, image_url: url })),
      model: 'birefnet'
    })
  });
  const { job_id, poll_url } = await response.json();
  // Poll poll_url until status is 'completed'
  return { job_id, poll_url };
}

The API also includes /image-review for automated quality validation, /virtual-try-on for fashion, and /product-description for AI-generated copy.

View Sirv Studio API docs →

CSS White Background Fallback

.product-image {
  background: white;
  object-fit: contain;
  aspect-ratio: 1;
}

/* Subtle shadow for depth */
.product-image-container {
  background: white;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.05);
  border-radius: 8px;
  padding: 20px;
}

Performance Optimization

Critical Product Image

<head>
  <!-- Preload main product image -->
  <link
    rel="preload"
    as="image"
    href="product-main.avif"
    type="image/avif"
  >
  <link
    rel="preload"
    as="image"
    href="product-main.webp"
    type="image/webp"
  >
</head>

Image Generation Pipeline

const sharp = require('sharp');

const PRODUCT_SIZES = [
  { name: 'zoom', width: 1600, quality: 90 },
  { name: 'main', width: 800, quality: 85 },
  { name: 'thumb', width: 400, quality: 80 },
  { name: 'cart', width: 150, quality: 75 }
];

const FORMATS = ['avif', 'webp', 'jpg'];

async function processProductImage(inputPath, productId) {
  const outputDir = `./products/${productId}`;

  for (const size of PRODUCT_SIZES) {
    for (const format of FORMATS) {
      const outputPath = `${outputDir}/${size.name}.${format}`;

      let pipeline = sharp(inputPath)
        .resize(size.width, size.width, {
          fit: 'contain',
          background: { r: 255, g: 255, b: 255 }
        });

      if (format === 'avif') {
        pipeline = pipeline.avif({ quality: size.quality - 10 });
      } else if (format === 'webp') {
        pipeline = pipeline.webp({ quality: size.quality });
      } else {
        pipeline = pipeline.jpeg({ quality: size.quality });
      }

      await pipeline.toFile(outputPath);
    }
  }
}

Batch Processing

const glob = require('glob');
const pLimit = require('p-limit');

const limit = pLimit(4); // Process 4 images concurrently

async function processAllProducts() {
  const images = glob.sync('./source-images/*.{jpg,png}');

  const tasks = images.map(imagePath => {
    const productId = path.basename(imagePath, path.extname(imagePath));
    return limit(() => processProductImage(imagePath, productId));
  });

  await Promise.all(tasks);
  console.log(`Processed ${images.length} product images`);
}

SEO Optimization

Alt Text Best Practices

<!-- Bad: Generic -->
<img alt="Product image">

<!-- Bad: Keyword stuffing -->
<img alt="buy cheap blue running shoes best price online store sale discount">

<!-- Good: Descriptive -->
<img alt="Nike Air Max 90 Running Shoes in Navy Blue - Side View">

<!-- Good: With context -->
<img alt="Model wearing the Classic Fit Oxford Shirt in White, Size Medium">

Structured Data

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Classic Running Shoes",
  "image": [
    "https://example.com/products/shoes-front.jpg",
    "https://example.com/products/shoes-side.jpg",
    "https://example.com/products/shoes-back.jpg"
  ],
  "description": "Comfortable running shoes for everyday use",
  "brand": {
    "@type": "Brand",
    "name": "ShoeBrand"
  },
  "offers": {
    "@type": "Offer",
    "price": "89.99",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock"
  }
}
</script>

Mobile Optimization

.product-thumbnails {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.product-thumbnails::-webkit-scrollbar {
  display: none;
}

.product-thumbnail {
  flex: 0 0 auto;
  scroll-snap-align: start;
  min-width: 60px;
  min-height: 60px; /* Touch target */
}
class SwipeGallery {
  constructor(container) {
    this.container = container;
    this.slides = container.querySelectorAll('.slide');
    this.currentIndex = 0;
    this.touchStartX = 0;
    this.touchEndX = 0;

    this.init();
  }

  init() {
    this.container.addEventListener('touchstart', (e) => {
      this.touchStartX = e.changedTouches[0].screenX;
    });

    this.container.addEventListener('touchend', (e) => {
      this.touchEndX = e.changedTouches[0].screenX;
      this.handleSwipe();
    });
  }

  handleSwipe() {
    const diff = this.touchStartX - this.touchEndX;
    const threshold = 50;

    if (Math.abs(diff) > threshold) {
      if (diff > 0 && this.currentIndex < this.slides.length - 1) {
        this.goTo(this.currentIndex + 1);
      } else if (diff < 0 && this.currentIndex > 0) {
        this.goTo(this.currentIndex - 1);
      }
    }
  }

  goTo(index) {
    this.currentIndex = index;
    this.container.style.transform = `translateX(-${index * 100}%)`;
  }
}

A/B Testing Images

Test Variables

Track these metrics when testing images:

VariableTest Options
Main imageProduct-only vs lifestyle
Image count3 vs 5 vs 8 images
First imageFront vs angled vs in-use
Zoom typeHover vs click vs lightbox
BackgroundPure white vs light gray vs styled

Implementation

// Simple A/B test
const variant = Math.random() > 0.5 ? 'A' : 'B';

const imageConfig = {
  A: {
    firstImage: 'product-front.jpg',
    imageCount: 4,
    zoomType: 'hover'
  },
  B: {
    firstImage: 'product-lifestyle.jpg',
    imageCount: 6,
    zoomType: 'lightbox'
  }
};

// Track in analytics
analytics.track('product_view', {
  variant,
  config: imageConfig[variant]
});

Summary

Quick Reference

ElementSpecification
Main image800×800, 85% quality, eager load
Zoom image1600×1600, 90% quality, on-demand
Thumbnails150×150, 75% quality, eager load
Listings400×400, 80% quality, lazy load
FormatAVIF → WebP → JPEG

Checklist

  1. ✅ Use square (1:1) aspect ratios for consistency
  2. ✅ Provide minimum 2000×2000 for quality zoom
  3. ✅ Include multiple angles (front, back, side, detail)
  4. ✅ Maintain color accuracy with sRGB profiles
  5. ✅ Implement responsive images with srcset
  6. ✅ Lazy load below-fold product images
  7. ✅ Preload main product image on PDP
  8. ✅ Add zoom capability (hover or lightbox)
  9. ✅ Optimize for mobile swipe gestures
  10. ✅ Write descriptive, specific alt text
  11. ✅ Include structured data with all image URLs
  12. ✅ Use CDN for automatic format selection
  13. ✅ Test image loading on slow connections
  14. ✅ A/B test image presentation strategies

Product images are often the deciding factor in purchase decisions. Invest in quality photography, implement proper optimization, and continuously test to maximize conversions.

Related Resources

Format References

Platform Guides

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial