How to Select Multiple Images in a Viewer with JavaScript
During document scanning, we may need to select multiple images for reordering, retouching, export, etc. It is useful to have a document viewer which supports selecting multiple images.
Dynamsoft Document Viewer is an SDK for such a purpose. It provides a set of viewers for documents. In this article, we are going to demonstrate how to use it to browse and select multiple images. In addition, we will also explore how to implement it from scratch.
Use Dynamsoft Document Viewer to Browse and Select Multiple Images
-
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>Browse 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 browse viewer, bind it to a container and use it to view the document we just created.
HTML:
<div id="viewer"></div>
JavaScript:
const browseViewer = new Dynamsoft.DDV.BrowseViewer({ container: document.getElementById("viewer"), }); browseViewer.openDocument(doc.uid);
CSS:
#viewer { width: 320px; height: 480px; }
-
Use
input
to select multiple image files and load them into the document instance so that we can use the browse viewer to view the files.HTML:
<label> Select images to load: <br/> <input type="file" id="files" name="files" multiple onchange="filesSelected()"/> </label>
JavaScript:
async function filesSelected(){ let filesInput = document.getElementById("files"); let files = filesInput.files; if (files.length>0) { for (let index = 0; index < files.length; index++) { const file = files[index]; 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 use hotkeys for multiple selection. Hold CTRL to select and unselect one image and hold SHIFT to select a range of images.
Browse Viewer uses Canvas internally which has a high performance.
Implement a Viewer for Multiple Selection from Scratch
So how does the multiple selection work? Let’s implement it in steps.
-
Create a container as the viewer. It contains a list of thumbnail containers.
<div id="viewer"> <div class="thumbnail selected"> <img src="blob:http://127.0.0.1:8000/d837ea7d-56aa-4d7c-baba-dde7503b72dd" style="height: 168px;"> </div> <div class="thumbnail"> <img src="blob:http://127.0.0.1:8000/62819aff-5f5b-4766-9b5c-92586bace2d6" style="height: 168px;"> </div> </div>
Styles:
It uses
flex
layout to align the views.The CSS for the viewer:
#viewer { display: flex; flex-wrap: wrap; align-content: flex-start; width: 320px; height: 480px; overflow: auto; background: lightgray; border: 1px solid black; }
The CSS for the thumbnails:
:root { --thumbnail-width: 140px; } .thumbnail { display: inline-flex; width: var(--thumbnail-width); height: 200px; padding: 5px; margin: 5px; align-items: center; justify-content: center; } .thumbnail:hover { background: gray; } .thumbnail.selected { background: gray; } .thumbnail.selected img { border: 1px solid orange; } .thumbnail img { width: 100%; max-height: 100%; object-fit: contain; border: 1px solid transparent; }
-
Load images from selected files. The height of the image is adjusted based on the image ratio and its container’s width.
let thumbnailWidth = 130; async function filesSelected(){ let filesInput = document.getElementById("files"); let files = filesInput.files; if (files.length>0) { for (let index = 0; index < files.length; index++) { const file = files[index]; const blob = await readFileAsBlob(file); const url = URL.createObjectURL(blob); appendImage(url); } } } function appendImage(url){ let viewer = document.getElementById("viewer"); let thumbnailContainer = document.createElement("div"); thumbnailContainer.className = "thumbnail"; let img = document.createElement("img"); img.src = url; img.onload = function(){ let height = thumbnailWidth/(img.naturalWidth/img.naturalHeight); img.style.height = Math.floor(height) + "px"; } thumbnailContainer.appendChild(img); viewer.appendChild(thumbnailContainer); }
-
Adjust the thumbnail’s width based on whether the scrollbar is shown. Here we do this by changing a custom CSS property.
function updateWidthBaseOnScrollBar(){ let viewer = document.getElementById("viewer"); if (viewer.scrollHeight>viewer.clientHeight) { let scrollBarWidth = viewer.offsetWidth - viewer.clientWidth; let width = 140 - Math.ceil(scrollBarWidth/2); document.documentElement.style.setProperty('--thumbnail-width', width + "px"); }else{ document.documentElement.style.setProperty('--thumbnail-width', "140px"); } }
-
Add click events for the thumbnails to select multiple images.
thumbnailContainer.addEventListener("click",function(){ const isMultiSelect = event.ctrlKey || event.metaKey; const isRangeSelect = event.shiftKey; const index = getIndex(thumbnailContainer); if (isMultiSelect) { toggleSelection(thumbnailContainer); } else if (isRangeSelect && lastSelectedIndex !== -1) { const firstSelectedIndex = getFirstSelectedIndex(); if (firstSelectedIndex != -1) { selectRange(firstSelectedIndex, index); }else{ selectRange(lastSelectedIndex, index); } } else { clearSelection(); selectOne(thumbnailContainer); } lastSelectedIndex = index; })
If no keys pressed, select only the clicked one.
clearSelection(); selectOne(thumbnailContainer); function selectOne(thumbnail) { thumbnail.classList.add('selected'); } function clearSelection() { let thumbnails = document.querySelectorAll(".thumbnail"); thumbnails.forEach(thumbnail => thumbnail.classList.remove('selected')); }
If the CTRL key is pressed, toggle the selection status of the clicked one.
function toggleSelection(thumbnail) { thumbnail.classList.toggle('selected'); }
If the SHIFT key is pressed, select images from the first selected index to the clicked index.
const firstSelectedIndex = getFirstSelectedIndex(); if (firstSelectedIndex != -1) { selectRange(firstSelectedIndex, index); }else{ selectRange(lastSelectedIndex, index); } function selectRange(start, end) { let thumbnails = document.querySelectorAll(".thumbnail"); clearSelection(); const [startIndex, endIndex] = start < end ? [start, end] : [end, start]; for (let i = startIndex; i <= endIndex; i++) { selectOne(thumbnails[i]); } }
Demo video: