在文档扫描过程中,我们可能需要选择多个图像以进行重新排序、编辑和导出等操作。这时我们通常需要一个支持多选的文档查看器。
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>
</head>
<style>
</style>
<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]);
}
}
源代码