How to Pan and Zoom an Image with JavaScript
Pan and zoom is a useful feature for viewing images. We can zoom in the image to see more details and perform image editing.
Dynamsoft Document Viewer is an SDK for such a purpose, which provides a set of viewers for document images. In this article, we are going to demonstrate how to use it to pan and zoom an image. In addition, we will also explore how to implement it from scratch with JavaScript.
Use Dynamsoft Document Viewer to Pan and Zoom an Image
-
Create a new HTML file with the following template.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Edit Viewer</title> <style> </style> </head> <body> </body> <script> </script> </html>
-
Include Dynamsoft Document Viewer’s files.
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/ddv.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/ddv.css">
-
Initialize Dynamsoft Document Viewer with a license. You can apply for one here.
Dynamsoft.DDV.Core.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial Dynamsoft.DDV.Core.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/engine";// Lead to a folder containing the distributed WASM files await Dynamsoft.DDV.Core.init();
-
Create a new document instance.
const docManager = Dynamsoft.DDV.documentManager; const doc = docManager.createDocument();
-
Create an instance of edit viewer, bind it to a container and use it to view the document we just created.
HTML:
<div id="viewer"></div>
JavaScript:
const uiConfig = { type: Dynamsoft.DDV.Elements.Layout, flexDirection: "column", className: "ddv-edit-viewer-mobile", children: [ Dynamsoft.DDV.Elements.MainView // the view which is used to display the pages ], }; editViewer = new Dynamsoft.DDV.EditViewer({ uiConfig: uiConfig, container: document.getElementById("viewer") }); editViewer.openDocument(doc.uid);
CSS:
#viewer { width: 320px; height: 480px; }
-
Use
input
to select an image file and load it into the document instance so that we can use the edit viewer to view the file.HTML:
<label> Select an image: <br/> <input type="file" id="files" name="files" onchange="filesSelected()"/> </label>
JavaScript:
async function filesSelected(){ let filesInput = document.getElementById("files"); let files = filesInput.files; if (files.length>0) { const file = files[0]; const blob = await readFileAsBlob(file); await doc.loadSource(blob); // load image } } function readFileAsBlob(file){ return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = async function(e){ const response = await fetch(e.target.result); const blob = await response.blob(); resolve(blob); }; fileReader.onerror = function () { reject('oops, something went wrong.'); }; fileReader.readAsDataURL(file); }) }
We can then hold the control key and use the mouse wheel to zoom in and zoom out and pan the image. On mobile devices, we can pinch to zoom. You can see how the image follows the mouse position or the touch position.
Implement Pan and Zoom from Scratch
There are several ways to implement pan and zoom.
- Use absolute pixel values. It is easy to understand and can have scrollbars.
- Use CSS transform. It can use GPU for a better performance but it cannot keep the scrollbars.
- Use Canvas. It has high performance and great customizability. Dynamsoft Document Viewer uses this method.
In the following, we are going to use the first way for demonstration.
-
Create a container as the viewer. It contains the image.
<div id="viewer"> <img id="image"/> </div>
Styles:
It uses
flex
layout to align the views.The CSS for the viewer:
#viewer { width: 320px; height: 480px; padding: 10px; border: 1px solid black; overflow: auto; display: flex; align-items: center; }
The CSS for the image:
#image { margin: auto; }
-
Load the selected image file. Fit the image’s width to the viewer.
let currentPercent; async function filesSelected(){ let filesInput = document.getElementById("files"); let files = filesInput.files; if (files.length>0) { const file = files[0]; const blob = await readFileAsBlob(file); const url = URL.createObjectURL(blob); loadImage(url); } } function loadImage(url){ let img = document.getElementById("image"); img.src = url; img.onload = function(){ let viewer = document.getElementById("viewer"); let percent = 1.0; resizeImage(percent); } } function resizeImage(percent){ currentPercent = percent; let img = document.getElementById("image"); let viewer = document.getElementById("viewer"); let borderWidth = 1; let padding = 10; let ratio = img.naturalWidth/img.naturalHeight; let newWidth = (viewer.offsetWidth - borderWidth*2 - padding*2) * percent img.style.width = newWidth + "px"; img.style.height = newWidth/ratio + "px"; } function readFileAsBlob(file){ return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = async function(e){ const response = await fetch(e.target.result); const blob = await response.blob(); resolve(blob); }; fileReader.onerror = function () { reject('oops, something went wrong.'); }; fileReader.readAsDataURL(file); }) }
-
Add a wheel event for zooming with the mouse with the control key pressed.
img.addEventListener("wheel",function(e){ if (e.ctrlKey || e.metaKey) { if (e.deltaY < 0) { zoom(true); }else{ zoom(false); } e.preventDefault(); } }); function zoom(zoomin,percentOffset){ let offset = percentOffset ?? 0.1; if (zoomin) { currentPercent = currentPercent + offset; }else{ currentPercent = currentPercent - offset; } currentPercent = Math.max(0.1,currentPercent); resizeImage(currentPercent); }
-
Add pointer events to implement pan with a mouse or a touchscreen.
let downPoint; let downScrollPosition; img.addEventListener("pointerdown",function(e){ previousDistance = undefined; downPoint = {x:e.clientX,y:e.clientY}; downScrollPosition = {x:viewer.scrollLeft,y:viewer.scrollTop} }); img.addEventListener("pointerup",function(e){ downPoint = undefined; }); img.addEventListener("pointermove",function(e){ if (downPoint) { let offsetX = e.clientX - downPoint.x; let offsetY = e.clientY - downPoint.y; let newScrollLeft = downScrollPosition.x - offsetX; let newScrollTop = downScrollPosition.y - offsetY; viewer.scrollLeft = newScrollLeft; viewer.scrollTop = newScrollTop; } });
-
Add a touch move event to support pinch to zoom. It calculates the distance between the two touches to know whether to zoom in or zoom out.
img.addEventListener("touchmove",function(e){ if (e.touches.length === 2) { const distance = getDistanceBetweenTwoTouches(e.touches[0],e.touches[1]); if (previousDistance) { if ((distance - previousDistance)>0) { //zoom zoom(true,0.02); }else{ zoom(false,0.02); } previousDistance = distance; }else{ previousDistance = distance; } } e.preventDefault(); }); function getDistanceBetweenTwoTouches(touch1,touch2){ const offsetX = touch1.clientX - touch2.clientX; const offsetY = touch1.clientY - touch2.clientY; const distance = offsetX * offsetX + offsetY + offsetY; return distance; }
All right, we’ve implemented the pan and zoom feature with JavaScript.