This guide covers advanced usage patterns for building sophisticated zoom image experiences.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/willnguyen1312/zoom-image/llms.txt
Use this file to discover all available pages before exploring further.
Programmatic Control
All zoom modes provide methods to control zoom behavior programmatically.Controlling Wheel Zoom State
Wheel zoom mode offers the most programmatic control through itssetState and getState methods.
import { createZoomImageWheel } from "@zoom-image/core";
const container = document.getElementById("container");
const result = createZoomImageWheel(container, {
maxZoom: 8,
});
// Get current state
const currentState = result.getState();
console.log('Current zoom:', currentState.currentZoom);
console.log('Position:', currentState.currentPositionX, currentState.currentPositionY);
console.log('Rotation:', currentState.currentRotation);
// Programmatically zoom in
function zoomIn() {
result.setState({
currentZoom: result.getState().currentZoom + 0.5,
});
}
// Programmatically zoom out
function zoomOut() {
result.setState({
currentZoom: result.getState().currentZoom - 0.5,
});
}
// Reset zoom
function resetZoom() {
result.setState({
currentZoom: 1,
});
}
// Rotate image
function rotate(degrees) {
result.setState({
currentRotation: result.getState().currentRotation + degrees,
});
}
// Enable/disable zoom
function toggleZoom() {
result.setState({
enable: !result.getState().enable,
});
}
Animated Zoom Transitions
Create smooth animated zoom effects:function animateZoomTo(targetZoom, duration = 300) {
const startZoom = result.getState().currentZoom;
const startTime = performance.now();
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function animate(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easeInOutQuad(progress);
const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;
result.setState({ currentZoom });
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Usage
animateZoomTo(3, 500); // Zoom to 300% over 500ms
Preset Zoom Levels
Implement preset zoom buttons:const presets = [1, 2, 4, 6];
let currentPresetIndex = 0;
function nextZoomPreset() {
currentPresetIndex = (currentPresetIndex + 1) % presets.length;
result.setState({
currentZoom: presets[currentPresetIndex],
});
}
function previousZoomPreset() {
currentPresetIndex = (currentPresetIndex - 1 + presets.length) % presets.length;
result.setState({
currentZoom: presets[currentPresetIndex],
});
}
Controlling Hover Zoom
Hover zoom has limited programmatic control but you can enable/disable it:import { createZoomImageHover } from "@zoom-image/core";
const result = createZoomImageHover(container, {
zoomTarget: document.getElementById('zoom-target'),
customZoom: { width: 400, height: 600 },
scale: 2,
});
// Toggle zoom on/off
function toggleHoverZoom() {
const currentState = result.getState();
result.setState({
enabled: !currentState.enabled,
});
}
Lifecycle Management
Proper lifecycle management prevents memory leaks and ensures smooth performance.Basic Cleanup
const result = createZoomImageWheel(container);
// Subscribe to state
const unsubscribe = result.subscribe(({ state }) => {
console.log('State changed:', state);
});
// When done (e.g., component unmounting, page navigation)
function cleanup() {
unsubscribe(); // Stop listening to state changes
result.cleanup(); // Remove event listeners and clean up DOM
}
Cleanup with Event Listeners
UseAbortController for managing additional event listeners:
const container = document.getElementById("container");
const result = createZoomImageWheel(container);
const controller = new AbortController();
const { signal } = controller;
// Add custom event listeners
const zoomInBtn = document.getElementById("zoom-in");
zoomInBtn.addEventListener('click', () => {
result.setState({ currentZoom: result.getState().currentZoom + 0.5 });
}, { signal });
const zoomOutBtn = document.getElementById("zoom-out");
zoomOutBtn.addEventListener('click', () => {
result.setState({ currentZoom: result.getState().currentZoom - 0.5 });
}, { signal });
// Subscribe to state
const unsubscribe = result.subscribe(({ state }) => {
document.getElementById('zoom-level').textContent =
`${Math.round(state.currentZoom * 100)}%`;
});
// Cleanup everything
function cleanup() {
controller.abort(); // Remove all custom event listeners
unsubscribe(); // Unsubscribe from state
result.cleanup(); // Clean up zoom instance
}
Single Page Application Pattern
For SPA frameworks (without using framework adapters):class ZoomImageComponent {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.zoomInstance = null;
this.unsubscribe = null;
this.controller = new AbortController();
}
mount() {
// Create zoom instance
this.zoomInstance = createZoomImageWheel(this.container, {
maxZoom: 6,
});
// Subscribe to state
this.unsubscribe = this.zoomInstance.subscribe(({ state }) => {
this.onStateChange(state);
});
// Add event listeners
this.setupControls();
}
setupControls() {
const { signal } = this.controller;
document.getElementById('zoom-in').addEventListener('click', () => {
this.zoomInstance.setState({
currentZoom: this.zoomInstance.getState().currentZoom + 0.5,
});
}, { signal });
document.getElementById('zoom-out').addEventListener('click', () => {
this.zoomInstance.setState({
currentZoom: this.zoomInstance.getState().currentZoom - 0.5,
});
}, { signal });
}
onStateChange(state) {
console.log('Zoom level:', state.currentZoom);
}
unmount() {
// Clean up in reverse order
this.controller.abort();
this.unsubscribe?.();
this.zoomInstance?.cleanup();
this.zoomInstance = null;
this.unsubscribe = null;
this.controller = new AbortController();
}
}
// Usage
const component = new ZoomImageComponent('container');
component.mount();
// Later, when navigating away
component.unmount();
Multiple Zoom Instances
Manage multiple independent zoom instances on the same page.Multiple Instances of Same Type
import { createZoomImageHover } from "@zoom-image/core";
const products = [
{ id: 'product-1', container: 'container-1', target: 'target-1' },
{ id: 'product-2', container: 'container-2', target: 'target-2' },
{ id: 'product-3', container: 'container-3', target: 'target-3' },
];
const zoomInstances = new Map();
products.forEach(product => {
const container = document.getElementById(product.container);
const zoomTarget = document.getElementById(product.target);
const instance = createZoomImageHover(container, {
zoomTarget: zoomTarget,
customZoom: { width: 300, height: 400 },
scale: 2,
});
zoomInstances.set(product.id, instance);
});
// Cleanup all instances
function cleanupAll() {
zoomInstances.forEach(instance => instance.cleanup());
zoomInstances.clear();
}
Gallery with Different Zoom Modes
import {
createZoomImageWheel,
createZoomImageHover,
createZoomImageMove,
} from "@zoom-image/core";
class ImageGallery {
constructor() {
this.instances = [];
}
addWheelZoom(containerId) {
const container = document.getElementById(containerId);
const instance = createZoomImageWheel(container, {
maxZoom: 4,
});
this.instances.push(instance);
return instance;
}
addHoverZoom(containerId, targetId) {
const container = document.getElementById(containerId);
const target = document.getElementById(targetId);
const instance = createZoomImageHover(container, {
zoomTarget: target,
customZoom: { width: 400, height: 600 },
scale: 2,
});
this.instances.push(instance);
return instance;
}
addMoveZoom(containerId) {
const container = document.getElementById(containerId);
const instance = createZoomImageMove(container, {
zoomFactor: 4,
});
this.instances.push(instance);
return instance;
}
cleanupAll() {
this.instances.forEach(instance => instance.cleanup());
this.instances = [];
}
}
// Usage
const gallery = new ImageGallery();
gallery.addWheelZoom('main-product');
gallery.addHoverZoom('thumbnail-1', 'zoom-target-1');
gallery.addHoverZoom('thumbnail-2', 'zoom-target-2');
gallery.addMoveZoom('detail-view');
// Later
gallery.cleanupAll();
Synchronized Zoom Instances
Synchronize zoom state across multiple instances:const container1 = document.getElementById('container-1');
const container2 = document.getElementById('container-2');
const instance1 = createZoomImageWheel(container1);
const instance2 = createZoomImageWheel(container2);
// Synchronize zoom level
instance1.subscribe(({ state }) => {
instance2.setState({
currentZoom: state.currentZoom,
currentRotation: state.currentRotation,
});
});
instance2.subscribe(({ state }) => {
instance1.setState({
currentZoom: state.currentZoom,
currentRotation: state.currentRotation,
});
});
Be careful with bidirectional synchronization to avoid infinite loops. Consider using a flag or checking if the state actually changed before synchronizing.
Image Cropping
Capture the current zoomed view as a cropped image.Basic Cropping
import { createZoomImageWheel, cropImage } from "@zoom-image/core";
const container = document.getElementById("container");
const result = createZoomImageWheel(container);
async function handleCrop() {
const state = result.getState();
const croppedImageUrl = await cropImage({
image: container.querySelector('img'),
currentZoom: state.currentZoom,
positionX: state.currentPositionX,
positionY: state.currentPositionY,
rotation: state.currentRotation,
});
// Use the cropped image
const img = document.getElementById('cropped-result');
img.src = croppedImageUrl;
img.hidden = false;
}
document.getElementById('crop-btn').addEventListener('click', handleCrop);
Download Cropped Image
async function downloadCroppedImage() {
const state = result.getState();
const croppedImageUrl = await cropImage({
image: container.querySelector('img'),
currentZoom: state.currentZoom,
positionX: state.currentPositionX,
positionY: state.currentPositionY,
rotation: state.currentRotation,
});
// Create download link
const link = document.createElement('a');
link.href = croppedImageUrl;
link.download = `cropped-image-${Date.now()}.png`;
link.click();
}
Upload Cropped Image
async function uploadCroppedImage() {
const state = result.getState();
const croppedImageUrl = await cropImage({
image: container.querySelector('img'),
currentZoom: state.currentZoom,
positionX: state.currentPositionX,
positionY: state.currentPositionY,
rotation: state.currentRotation,
});
// Convert data URL to blob
const response = await fetch(croppedImageUrl);
const blob = await response.blob();
// Upload
const formData = new FormData();
formData.append('image', blob, 'cropped.png');
await fetch('/api/upload', {
method: 'POST',
body: formData,
});
}
Crop with Custom Dimensions
ThecropImage function respects the rotation and current viewport:
async function cropWithRotation() {
const state = result.getState();
const img = container.querySelector('img');
const croppedUrl = await cropImage({
image: img,
currentZoom: state.currentZoom,
positionX: state.currentPositionX,
positionY: state.currentPositionY,
rotation: state.currentRotation, // 0, 90, 180, or 270
});
// The cropped image will be rotated accordingly
return croppedUrl;
}
Integration Patterns
Modal/Lightbox Integration
class ZoomLightbox {
constructor() {
this.zoomInstance = null;
this.controller = new AbortController();
}
open(imageSrc) {
// Create modal
const modal = document.createElement('div');
modal.id = 'zoom-modal';
modal.className = 'fixed inset-0 bg-black/90 z-50 flex items-center justify-center';
modal.innerHTML = `
<button id="close-modal" class="absolute top-4 right-4 text-white">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div id="zoom-container" class="w-[80vw] h-[80vh]">
<img src="${imageSrc}" alt="Zoomed image" class="w-full h-full object-contain" />
</div>
`;
document.body.appendChild(modal);
// Initialize zoom
const container = document.getElementById('zoom-container');
this.zoomInstance = createZoomImageWheel(container, {
maxZoom: 8,
});
// Close on button click
const { signal } = this.controller;
document.getElementById('close-modal').addEventListener('click', () => {
this.close();
}, { signal });
// Close on escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.close();
}, { signal });
}
close() {
this.controller.abort();
this.zoomInstance?.cleanup();
document.getElementById('zoom-modal')?.remove();
this.zoomInstance = null;
this.controller = new AbortController();
}
}
// Usage
const lightbox = new ZoomLightbox();
document.querySelectorAll('.gallery-image').forEach(img => {
img.addEventListener('click', () => {
lightbox.open(img.src);
});
});
E-commerce Product Viewer
class ProductViewer {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.mainZoom = null;
this.thumbnailZooms = [];
this.currentImageIndex = 0;
this.images = [];
}
init(images) {
this.images = images;
this.renderMainImage();
this.renderThumbnails();
}
renderMainImage() {
const mainContainer = this.container.querySelector('.main-image');
mainContainer.innerHTML = `<img src="${this.images[this.currentImageIndex]}" alt="Product" />`;
this.mainZoom?.cleanup();
this.mainZoom = createZoomImageWheel(mainContainer, {
maxZoom: 6,
wheelZoomRatio: 0.1,
});
}
renderThumbnails() {
const thumbnailContainer = this.container.querySelector('.thumbnails');
thumbnailContainer.innerHTML = '';
this.thumbnailZooms.forEach(zoom => zoom.cleanup());
this.thumbnailZooms = [];
this.images.forEach((src, index) => {
const wrapper = document.createElement('div');
wrapper.className = 'thumbnail';
wrapper.innerHTML = `<img src="${src}" alt="Thumbnail ${index + 1}" />`;
wrapper.addEventListener('click', () => {
this.currentImageIndex = index;
this.renderMainImage();
});
const zoomTarget = document.createElement('div');
zoomTarget.className = 'thumbnail-zoom-target';
wrapper.appendChild(zoomTarget);
const thumbnailZoom = createZoomImageHover(wrapper, {
zoomTarget: zoomTarget,
customZoom: { width: 200, height: 200 },
scale: 1.5,
});
this.thumbnailZooms.push(thumbnailZoom);
thumbnailContainer.appendChild(wrapper);
});
}
cleanup() {
this.mainZoom?.cleanup();
this.thumbnailZooms.forEach(zoom => zoom.cleanup());
}
}
// Usage
const viewer = new ProductViewer('product-viewer');
viewer.init([
'/images/product-1.jpg',
'/images/product-2.jpg',
'/images/product-3.jpg',
]);
Responsive Zoom Mode Switching
Switch between zoom modes based on viewport size:class ResponsiveZoom {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.currentInstance = null;
this.currentMode = null;
}
init() {
this.updateZoomMode();
window.addEventListener('resize', () => this.updateZoomMode());
}
updateZoomMode() {
const width = window.innerWidth;
let newMode;
if (width < 768) {
newMode = 'click'; // Mobile: click to zoom
} else if (width < 1024) {
newMode = 'move'; // Tablet: move to zoom
} else {
newMode = 'hover'; // Desktop: hover to zoom
}
if (newMode !== this.currentMode) {
this.currentInstance?.cleanup();
this.currentMode = newMode;
this.initZoomMode(newMode);
}
}
initZoomMode(mode) {
switch (mode) {
case 'hover':
const target = document.getElementById('zoom-target');
this.currentInstance = createZoomImageHover(this.container, {
zoomTarget: target,
customZoom: { width: 400, height: 600 },
scale: 2,
});
break;
case 'move':
this.currentInstance = createZoomImageMove(this.container, {
zoomFactor: 4,
});
break;
case 'click':
this.currentInstance = createZoomImageClick(this.container, {
zoomFactor: 4,
});
break;
}
}
cleanup() {
this.currentInstance?.cleanup();
}
}
// Usage
const responsiveZoom = new ResponsiveZoom('container');
responsiveZoom.init();
Performance Optimization
Lazy load zoom instances
Lazy load zoom instances
Only initialize zoom when the image becomes visible:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const container = entry.target;
const instance = createZoomImageWheel(container);
observer.unobserve(container);
}
});
});
document.querySelectorAll('.zoom-container').forEach(container => {
observer.observe(container);
});
Debounce state updates
Debounce state updates
When displaying zoom information, debounce updates to reduce re-renders:
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const updateZoomInfo = debounce((state) => {
document.getElementById('zoom-level').textContent =
`${Math.round(state.currentZoom * 100)}%`;
}, 16); // ~60fps
result.subscribe(({ state }) => {
updateZoomInfo(state);
});
Preload high-resolution images
Preload high-resolution images
Preload zoom images before they’re needed:
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
// Preload on hover
container.addEventListener('mouseenter', async () => {
await preloadImage('/images/product-high-res.jpg');
// Image is now cached
}, { once: true });
Next Steps
Vanilla JS Guide
Complete vanilla JavaScript examples
Configuration
All configuration options explained
React Integration
Use Zoom Image with React
API Reference
Detailed API documentation