在文档扫描过程中,我们可能需要选择多个图像以进行重新排序、编辑和导出等操作。这时我们通常需要一个支持多选的文档查看器。
Dynamsoft Document Viewer是一个提供该种功能的SDK。它提供了一组查看器用于文档相关的操作。在本文中,我们将演示如何使用它来浏览和选择多张图片。此外,我们还将探讨如何从头实现这样一个查看器。
使用Dynamsoft Document Viewer浏览和选择多个图像
-
创建一个包含以下模板的新HTML文件。
<!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>
-
在页面中包含Dynamsoft Document Viewer的文件。
<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">
-
使用许可证初始化Dynamsoft Document Viewer。可以在这里申请一个证书。
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();
-
创建一个新的文档实例。
const docManager = Dynamsoft.DDV.documentManager; const doc = docManager.createDocument();
-
创建一个Browse Viewer实例,将其绑定到一个容器,然后用它来查看我们刚刚创建的文档。
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; }
-
使用
input
选择多个图像文件并将其加载到文档实例中,然后可以用Browse Viewer进行查看。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); }) }
我们可以使用热键进行多选。按住CTRL键可选择和取消选择一个图像,按住SHIFT键可选择一系列图像。
Browse Viewer内部使用Canvas实现,有较好的性能。
从头开始实现支持多选的查看器
那么我们可以如何实现多选?下面是分步实现过程。
-
创建一个容器作为查看器。它会列出文档图像的缩略图。
<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>
样式:
它使用
flex
布局来对齐组件。查看器的CSS:
#viewer { display: flex; flex-wrap: wrap; align-content: flex-start; width: 320px; height: 480px; overflow: auto; background: lightgray; border: 1px solid black; }
缩略图的CSS:
: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; }
-
从所选文件加载图像。根据图像比率及其容器的宽度对图像元素的高度进行调整。
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); }
-
根据是否存在滚动条调整缩略图的宽度。这里,我们通过更改自定义CSS属性来实现这一点。
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"); } }
-
为缩略图添加单击事件以选择多个图像。
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; })
如果没有按下任何键,选择被点击的单张图。
clearSelection(); selectOne(thumbnailContainer); function selectOne(thumbnail) { thumbnail.classList.add('selected'); } function clearSelection() { let thumbnails = document.querySelectorAll(".thumbnail"); thumbnails.forEach(thumbnail => thumbnail.classList.remove('selected')); }
如果按下CTRL键,则切换被单击图的选择状态。
function toggleSelection(thumbnail) { thumbnail.classList.toggle('selected'); }
如果按下SHIFT键,则选中从第一个被选图到当前被点击的图的所有图片。
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]); } }
演示视频: