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