Interactive demos of hcg-pinch-zoom, a lightweight zero-dependency JavaScript
library for pinch and wheel zoom. Try touch pinch, touchpad pinch
(wheel + ctrlKey), Ctrl + mouse wheel, slider zoom with
setScale(), built-in drag while zoomed,
minScale / maxScale. Version .
hcg-pinch-zoom - Full documentation
Add data-hcg-pinch-zoom to a viewport. The first child is the zoomable content.
<div class="pz-viewport" data-hcg-pinch-zoom>
<img src="photo.jpg" alt="Photo">
</div>
On touch devices, use two fingers to pinch zoom. Large diagram content shows pan after zooming in.
<div class="pz-viewport diagram-viewport">
<div class="pz-diagram">
<h3>Architecture diagram</h3>
<p>Pinch with two fingers on a phone or tablet.</p>
</div>
</div>
hcgPinchZoom('.diagram-viewport', {
pinch: true,
drag: true
});
Hold Ctrl and scroll the mouse wheel, or use a trackpad pinch gesture. Both use the same wheel handler with ctrlKey.
<div class="pz-viewport" id="viewport-wheel">
<img src="photo.jpg" alt="Photo">
</div>
hcgPinchZoom('#viewport-wheel', {
wheel: true
});
Use an HTML range input with setScale() for zoom. onZoom updates the slider when pinching or scrolling.
<label for="zoom-slider">Scale: <span id="slider-readout">1.00</span></label>
<input type="range" id="zoom-slider" min="0.5" max="4" step="0.01" value="1">
<div class="pz-viewport" id="viewport-step">
<img src="photo.jpg" alt="Photo">
</div>
<button type="button" id="btn-slider-reset">reset()</button>
const slider = document.getElementById('zoom-slider');
const readout = document.getElementById('slider-readout');
const viewport = document.getElementById('viewport-step');
const pz = hcgPinchZoom('#viewport-step', {
minScale: 0.5,
maxScale: 4,
onZoom(detail) {
slider.value = detail.scale;
readout.textContent = detail.scale.toFixed(2);
}
});
slider.addEventListener('input', () => {
const v = Number(slider.value);
const rect = viewport.getBoundingClientRect();
pz.setScale(v, {
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
});
});
document.getElementById('btn-slider-reset').addEventListener('click', () => {
pz.reset();
slider.value = '1';
readout.textContent = '1.00';
});
Zoom in or out with Ctrl+wheel or pinch, then drag with one finger or mouse. Drag is built in: when scale is 100%, drag is off and position resets.
<div class="pz-viewport" id="viewport-pan">
<img src="photo.jpg" alt="Photo">
</div>
<button type="button" id="btn-pan-reset">reset()</button>
const pz = hcgPinchZoom('#viewport-pan', {
drag: true,
onPan(detail) {
console.log(detail.x, detail.y, detail.scale);
}
});
document.getElementById('btn-pan-reset').addEventListener('click', () => {
pz.reset();
});
Limit zoom range with data-hcg-min-scale and data-hcg-max-scale (here 1 to 2.5).
<div class="pz-viewport" data-hcg-pinch-zoom data-hcg-min-scale="1" data-hcg-max-scale="2.5">
<img src="photo.jpg" alt="Photo">
</div>
Call instance methods to reset or jump to a scale level programmatically.
Scale: 1
<p id="scale-readout">Scale: 1</p>
<div class="pz-viewport" id="viewport-api">
<img src="photo.jpg" alt="Photo">
</div>
<button type="button" id="btn-zoom-in">setScale(2)</button>
<button type="button" id="btn-zoom-out">setScale(1)</button>
<button type="button" id="btn-reset">reset()</button>
const pz = hcgPinchZoom('#viewport-api', {
onZoom(detail) {
document.getElementById('scale-readout').textContent =
'Scale: ' + detail.scale.toFixed(2);
}
});
document.getElementById('btn-zoom-in').addEventListener('click', () => {
pz.setScale(2);
});
document.getElementById('btn-zoom-out').addEventListener('click', () => {
pz.setScale(1);
});
document.getElementById('btn-reset').addEventListener('click', () => {
pz.reset();
});
// Or without storing the instance:
hcgPinchZoom.reset('#viewport-api');
minScale: 0.5 lets you zoom out to 50%. Drag is enabled whenever scale is not 100%.
<div class="pz-viewport" id="viewport-contain">
<img src="photo.jpg" alt="Photo">
</div>
<button type="button" id="btn-contain-reset">reset()</button>
const pz = hcgPinchZoom('#viewport-contain', {
minScale: 0.5
});
document.getElementById('btn-contain-reset').addEventListener('click', () => {
pz.reset();
});
Buttons and links inside zoomable content stay clickable, including after zoom. The library skips pan on interactive elements (button, a, input, and data-hcg-pinch-zoom-interactive). Demo images use pointer-events: none; interactive children must not. Zoom in, then click a region.
Last click: none
<p>Last click: <span id="click-readout">none</span></p>
<div class="pz-viewport" id="viewport-clickable">
<div class="pz-interactive">
<h3>Interactive map</h3>
<button type="button" class="pz-hotspot" data-region="North">North</button>
<!-- more buttons -->
</div>
</div>
hcgPinchZoom('#viewport-clickable', { drag: true });
document.querySelectorAll('.pz-hotspot').forEach((btn) => {
btn.addEventListener('click', () => {
document.getElementById('click-readout').textContent = btn.dataset.region;
});
});
/* Do not use pointer-events: none on interactive children.
For zoom-only (no pan), use drag: false so clicks work while zoomed. */
Set drag: false to zoom only. Useful for image viewers where pan is not needed.
<div class="pz-viewport" id="viewport-drag-off">
<img src="photo.jpg" alt="Photo">
</div>
hcgPinchZoom('#viewport-drag-off', {
drag: false
});