How to Build a Browser-Based PDF Redaction Tool Using JavaScript

TL;DR · AI 摘要
本文提供了一个基于JavaScript的浏览器PDF红action工具的构建教程,涵盖坐标转换、库使用和PDF处理流程。
核心要点
- 使用PDF.js库实现浏览器端PDF渲染与操作
- 需将Canvas坐标转换为PDF页面坐标系实现精准红action
- 工具支持多页面选择和红action区域管理
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 浏览器PDF红action工具
- 核心技术
- PDF.js库
- Canvas坐标转换
- 实现流程
- 上传处理
- 红action绘制
- PDF生成
金句 / Highlights
值得收藏与分享的关键句。
红action必须修改PDF内容而非简单覆盖,否则隐藏信息可能被恢复
通过PDF.js和Canvas实现浏览器端PDF渲染与坐标转换
浏览器端处理避免后端依赖,提升隐私安全性
如何使用 JavaScript 构建基于浏览器的 PDF 红action工具
2026年7月17日
/
Bhavin Sheth
PDF 文档常用于共享发票、合同、报告、法律记录、客户文件、财务报表和内部业务文件。但在这些文档共享之前,可能包含不应让接收者看到的信息。
发票可能包含账户号码。客户文件可能包含家庭地址或电话号码。法律文件可能暴露机密案件信息,而内部报告可能包含仅限员工查看的姓名、参考信息或业务数据。
这就是 PDF 红action 的作用所在。
红action 允许用户选择文档中的敏感区域,并在生成新 PDF 之前永久覆盖这些区域。实用的红action 工具还应支持多区域红action、页面选择、文档预览和最终输出验证。
在本教程中,你将使用 JavaScript 构建一个基于浏览器的 PDF 红action 工具。用户将上传 PDF,浏览其页面,在文档预览上直接绘制红action 方框,管理多个红action 区域,将其应用到所选页面,预览处理后的文档,重命名最终文件,并下载红action 后的 PDF。
整个工作流程在浏览器中运行。这对于注重隐私的文档工具特别有用,因为 PDF 处理可以在本地完成,无需后端服务器。
完成本教程后,你将不仅了解如何绘制红action 区域,还将了解如何将浏览器坐标转换为 PDF 坐标,并将这些选择应用到实际文档中。
目录
- 红action 并非等同于绘制黑色方框
- 基于浏览器的 PDF 红action 工作原理
- 理解 PDF 和画布坐标
- 项目设置
- 我们使用了哪些库?
- 创建 PDF 上传界面
- 预览上传的 PDF 页面
- 在 PDF 上绘制红action 区域
- 存储和管理红action
- 将红action 应用到所选页面
- 生成红action 后的 PDF
- 预览和重命名最终 PDF
- 下载最终 PDF
- 演示:PDF 红action 工具的工作方式
- 如何验证红action 后的 PDF
- 性能优化技巧
- 重要注意事项和常见错误
- 结论
红action 并非等同于绘制黑色方框
常见的错误是假设在文本上放置一个黑色矩形就能自动使信息安全。
从视觉上看,文档可能看起来已被红action。但根据 PDF 的修改方式,原始文本或图像内容可能仍然存在于矩形下方。
例如,想象在账户号码上方添加一个黑色方框作为新的注释层。该号码在页面上不再可见,但底层的 PDF 内容可能仍然存在。
在一些红action 不彻底的文档中,用户可能能够选择、复制、搜索或恢复隐藏的内容。
这就是为什么红action 必须与简单的视觉装饰区分开来。
在我们的浏览器端工作流程中,所选区域会在生成处理后的 PDF 时应用。最终文档在共享之前应仔细检查。
千万不要假设一个黑色矩形就能保证彻底移除底层 PDF 内容。对于高安全级别或法律敏感的文档,最终文件应通过专门的红action 验证流程进行验证。
基于浏览器的 PDF 红action 工作原理
红框处理流程可分为几个明确的阶段。
首先,浏览器读取上传的PDF文件并渲染页面预览。预览为用户提供了一个可视界面,用于识别敏感信息。
接下来,用户点击并拖动预览区域以创建红框矩形。
每个矩形以一组坐标的形式存储。
const redaction = {
page: 7,
x: 420,
y: 35,
width: 310,
height: 220
};应用程序可以为同一页存储多个矩形。
redactions.push(redaction);当用户点击"应用并最终确认"时,应用程序会确定哪些页面需要应用所选的红框。
随后,红框坐标会从预览坐标转换为实际PDF页面坐标。最后,应用程序修改PDF并生成新文档用于预览和下载。
整体工作流程如下:
上传PDF
↓
渲染页面预览
↓
绘制红框区域
↓
存储坐标
↓
选择目标页面
↓
应用红框
↓
生成新PDF
↓
预览和下载将界面与PDF处理逻辑分离可以使应用程序更易于管理和调试。
理解PDF和画布坐标
该项目最重要的技术部分之一是坐标转换。
浏览器中显示的PDF页面通常会根据可用屏幕空间进行缩放。一个PDF页面的实际宽度可能是842个点,而浏览器预览可能仅显示600像素宽。
这意味着在预览中绘制的x=300的矩形,不能简单地直接放置到PDF的x=300位置。
我们首先需要计算缩放比例差异。
const scaleX =
pdfPageWidth / canvasWidth;
const scaleY =
pdfPageHeight / canvasHeight;然后可以将所选矩形进行转换。
const pdfX =
redaction.x * scaleX;
const pdfWidth =
redaction.width * scaleX;
const pdfHeight =
redaction.height * scaleY;Y坐标需要特别注意,因为浏览器画布和PDF页面通常使用不同的坐标原点。
画布坐标通常从左上角开始,而PDF坐标通常从左下角开始。
因此Y坐标可以这样转换:
const pdfY =
pdfPageHeight -
((redaction.y + redaction.height) * scaleY);这个小计算至关重要。
如果坐标转换不正确,用户在浏览器中绘制的红框可能在生成的PDF中偏离数厘米的位置。
精确的坐标映射可以确保用户在浏览器中绘制的红框与最终文档中的相同区域完全匹配。
项目设置
由于红框处理流程完全在浏览器中运行,我们将保持项目结构简单。
创建包含三个文件的新项目文件夹:
pdf-redaction-tool/
│
├── index.html
├── style.css
└── script.jsindex.html文件包含上传界面、PDF预览、红框控制和最终下载部分。
style.css文件处理页面布局和红框覆盖层样式。
script.js文件包含PDF加载、渲染、坐标跟踪、红框管理和最终PDF生成逻辑。
从基本的HTML结构开始。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0">
<title>PDF 敏感信息抹除工具</title>
<link
rel="stylesheet"
href="style.css">
</head>
<body>
<main id="app">
<section id="uploadSection"></section>
<section id="editorSection"></section>
<section id="resultSection"></section>
</main>
<script src="script.js"></script>
</body>
</html>将上传、编辑和结果区域分开,可以更方便地在用户进行敏感信息抹除流程时显示和隐藏界面的不同部分。
我们使用了哪些库?
本项目将使用 PDF.js 和 PDF-lib。
PDF.js 负责 PDF 的加载和页面渲染。它允许我们将上传的 PDF 页面显示在画布中,让用户可以直观地选择需要抹除的区域。
PDF-lib 负责最终的文档修改。在用户创建抹除区域后,PDF-lib 会打开原始 PDF,访问选中的页面,应用抹除矩形后生成新文件。
在主 JavaScript 文件之前添加这两个库的引用。
<script
src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js">
</script>
<script
src="https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js">
</script>
<script src="script.js"></script>配置 PDF.js 的 worker。
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";我们还将创建一些变量用于存储当前文档和抹除数据。
let pdfDocument = null;
let pdfBytes = null;
let currentPage = 1;
let redactions = {};与其将所有抹除信息存储在一个扁平数组中,不如按页码进行分类存储。
redactions = {
1: [],
2: [],
7: []
};这种结构使页面导航和每页的抹除管理变得更加简单。
创建 PDF 上传界面
用户看到的第一个界面是 PDF 上传区域。
用户可以将文档拖到上传框中,或点击 "选择 PDF" 按钮打开浏览器的文件选择器。
创建上传界面。
<section id="uploadSection">
<h1>PDF 敏感信息抹除工具</h1>
<p>
上传您的 PDF 文件以永久遮盖
敏感信息。
</p>
<div id="dropZone" class="drop-zone">
<div class="upload-icon">☁</div>
<h2>将 PDF 文件拖放至此处</h2>
<p>或点击以浏览文件</p>
<button id="selectPdfButton">
选择 PDF
</button>
<input
id="pdfInput"
type="file"
accept="application/pdf"
hidden>
</div>
</section>将按钮连接到隐藏的文件输入框。
const pdfInput =
document.getElementById("pdfInput");
const selectPdfButton =
document.getElementById("selectPdfButton");
selectPdfButton.addEventListener("click", () => {
pdfInput.click();
});接下来,监听文件选择操作。
pdfInput.addEventListener("change", async event => {
const file = event.target.files[0];
if (!file) {
return;
}
await loadPdfFile(file);
});在加载文档之前,先验证所选文件。
async function loadPdfFile(file) {
if (file.type !== "application/pdf") {
alert("请选择有效的 PDF 文件。");
return;
}
pdfBytes =
await file.arrayBuffer();
}我们还可以支持拖放上传功能。
dropZone.addEventListener("dragover", event => {
event.preventDefault();
dropZone.classList.add("dragging");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragging");
});
dropZone.addEventListener("drop", async event => {
event.preventDefault();
dropZone.classList.remove("dragging");
const file = event.dataTransfer.files[0];
if (file) {
await loadPdfFile(file);
}
});
读取文件后,使用 PDF.js 加载文件。
pdfDocument = await pdfjsLib .getDocument({ data: pdfBytes.slice(0) }) .promise;
currentPage = 1;
await renderPage(currentPage);
此时文档已准备好进行预览和编辑。
## 预览上传的 PDF 页面
PDF 加载完成后,应用程序会在画布中显示当前页面。
该画布有两个作用。首先,它为用户提供文档的精确预览。其次,它成为用户绘制编辑区域的可视化表面。
创建编辑器预览。
<section id="editorSection">
<div class="preview-wrapper">
<div id="pageContainer">
<canvas id="pdfCanvas"></canvas>
<div id="redactionLayer"></div>
</div>
<div class="page-navigation">
<button id="previousPage"> < </button>
<span id="pageInfo"> Page 1 of 1 </span>
<button id="nextPage"> > </button>
</div>
</div>
</section>
pdfCanvas 显示 PDF 页面。
redactionLayer 位于画布上方,包含用户创建的选择框。
使用 PDF.js 渲染当前页面。
async function renderPage(pageNumber) {
const page = await pdfDocument.getPage(pageNumber);
const viewport = page.getViewport({ scale: 1.4 });
const canvas = document.getElementById("pdfCanvas");
const context = canvas.getContext("2d");
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
updatePageInformation();
renderSavedRedactions();
}
更新页面导航信息。
function updatePageInformation() {
document .getElementById("pageInfo") .textContent = Page ${currentPage} of ${pdfDocument.numPages};
}
用户可以切换到上一页。
previousPage.addEventListener("click", async () => {
if (currentPage <= 1) { return; }
currentPage--;
await renderPage(currentPage);
});
下一页按钮的工作方式相同。
nextPage.addEventListener("click", async () => {
if ( currentPage >= pdfDocument.numPages ) { return; }
currentPage++;
await renderPage(currentPage);
});
每当用户切换页面时,画布会渲染所选 PDF 页面,并恢复该页面已保存的编辑框。
这一点非常重要,因为编辑操作是针对特定页面的。在第 7 页创建的矩形不应自动出现在第 8 页,除非用户后续选择将该选择应用到多页。
PDF 现在已加载、渲染,并准备接受用户交互。
## 在 PDF 上绘制编辑区域
现在 PDF 页面已经可见,用户需要一种简单的方式来标记他们想要隐藏的信息。
在这个项目中,用户可以直接点击并拖动页面预览来创建一个删除标记矩形。交互方式类似于在图像编辑器中选择一个区域。
我们首先跟踪指针的起始位置。
let isDrawing = false;
let startX = 0; let startY = 0;
let activeBox = null;
在删除标记层上监听指针按下事件。
redactionLayer.addEventListener( "pointerdown", event => {
isDrawing = true;
const bounds = redactionLayer.getBoundingClientRect();
startX = event.clientX - bounds.left;
startY = event.clientY - bounds.top;
activeBox = document.createElement("div");
activeBox.className = "redaction-box";
activeBox.style.left = ${startX}px;
activeBox.style.top = ${startY}px;
redactionLayer.appendChild(activeBox);
} );
当指针移动时,更新矩形的尺寸。
redactionLayer.addEventListener( "pointermove", event => {
if (!isDrawing) { return; }
const bounds = redactionLayer.getBoundingClientRect();
const currentX = event.clientX - bounds.left;
const currentY = event.clientY - bounds.top;
const width = Math.abs(currentX - startX);
const height = Math.abs(currentY - startY);
activeBox.style.width = ${width}px;
activeBox.style.height = ${height}px;
activeBox.style.left = ${Math.min(startX, currentX)}px;
activeBox.style.top = ${Math.min(startY, currentY)}px;
} );
Math.min() 的调用非常重要,因为用户可能以任何方向拖动。他们可以从左上角开始向下拖动,或者从右下角开始向上拖动。
当指针释放时,保存完成的矩形。
redactionLayer.addEventListener( "pointerup", () => {
if (!isDrawing) { return; }
isDrawing = false;
saveRedaction(activeBox);
activeBox = null;
} );
在编辑过程中,删除标记区域可以样式化为半透明的黑色矩形。
.redaction-box { position: absolute; background: rgba(0, 0, 0, 0.8); border: 1px dashed #ffffff; cursor: move; }
使用透明预览可以帮助用户看到当前被覆盖的内容,同时仍然能够识别周围的页面。
## 存储和管理删除标记
绘制矩形只是第一步。应用程序还需要记住其位置和尺寸。
当删除标记框完成时,读取其当前坐标。
function saveRedaction(box) {
const redaction = {
x: parseFloat(box.style.left),
y: parseFloat(box.style.top),
width: box.offsetWidth,
height: box.offsetHeight
};
if (!redactions[currentPage]) {
redactions[currentPage] = [];
}
redactions[currentPage].push(redaction);
renderSavedRedactions();
updateRedactionList();
}
由于删除标记是按页面存储的,用户可以在文档中导航而不会丢失选择。
例如:
redactions = {
7: [
{
x: 420,
y: 35,
width: 310,
height: 220
},
{
x: 160,
y: 320,
width: 150,
height: 140
}
]
};第 7 页现在包含两个红action区域。
界面可以将它们显示为 红action #1 和 红action #2 。
function updateRedactionList() {
const list =
document.getElementById(
"redactionList"
);
list.innerHTML = "";
const pageRedactions =
redactions[currentPage] || [];
pageRedactions.forEach(
(redaction, index) => {
const item =
document.createElement("div");
item.textContent =
`Redaction #${index + 1}`;
list.appendChild(item);
}
);
}此列表为用户提供当前页面所选区域的清晰概览。
删除一个红action
用户可能会不小心覆盖文档的错误部分。他们不应该需要清除所有选择并重新开始。
为每个红action项添加删除按钮。
const removeButton =
document.createElement("button");
removeButton.textContent = "×";
removeButton.addEventListener(
"click",
() => {
removeRedaction(index);
}
);
item.appendChild(removeButton);仅删除所选矩形。
function removeRedaction(index) {
redactions[currentPage]
.splice(index, 1);
renderSavedRedactions();
updateRedactionList();
}剩余的红action保持不变。
清除当前页面的所有红action
"此页面全部清除"按钮会从当前页面移除所有选择。
clearPageButton.addEventListener(
"click",
() => {
redactions[currentPage] = [];
renderSavedRedactions();
updateRedactionList();
}
);请注意,这不会删除其他页面创建的红action。
如果清除第 7 页,保存在第 2 页或第 5 页的选择仍然可用。
将红action应用到所选页面
红action工具需要处理多页文档。
有时敏感信息只出现一次。在其他文档中,相同信息可能在多个页面重复出现。
例如,机密参考编号可能出现在每页页眉。手动绘制相同的矩形 50 次效率低下。
我们的界面提供三种页面选择模式:
- 仅当前页面:仅将红action应用到当前页面。
- 所有页面:将当前页面的红action位置复制到整个文档。
- 指定页面:仅将选择应用到用户输入的页面编号或范围。
创建页面选择控件。
<h3>3. Apply to Pages</h3>
<label>
<input
type="radio"
name="applyMode"
value="current"
checked>
Current page only
</label>
<label>
<input
type="radio"
name="applyMode"
value="all">
All pages
</label>
<label>
<input
type="radio"
name="applyMode"
value="specific">
Specific pages
</label>
<input
id="pageRange"
type="text"
placeholder="e.g., 1, 3-5, 10">读取所选模式。
const applyMode =
document.querySelector(
'input[name="applyMode"]:checked'
).value;对于当前页面,只需要一个页面编号。
if (applyMode === "current") {
/think
在处理之前,移除无效的页面编号。
targetPages = targetPages.filter(page =>
page >= 1 &&
page <= pdfDocument.numPages
);
此页面选择功能对于重复的页眉、页脚、文档ID或其他信息特别有用,这些信息在多个页面上位置一致。
### 应用和最终确定红action
在用户创建了红action区域并选择了目标页面后,他们可以点击“应用并最终确定”。
创建操作按钮。
<div class="action-buttons">
<button id="applyButton"> Apply & Finalize </button>
<button id="startOverButton"> Start Over </button>
</div>
将最终确定按钮连接到处理函数。
applyButton.addEventListener( "click", async () => {
const pageRedactions = redactions[currentPage] || [];
if (pageRedactions.length === 0) {
alert( "Please add at least one redaction." );
return;
}
await generateRedactedPdf();
} );
“重新开始”按钮会清除当前文档,并将用户返回到上传界面。
startOverButton.addEventListener( "click", () => {
pdfDocument = null;
pdfBytes = null;
currentPage = 1;
redactions = {};
pdfInput.value = "";
location.reload();
} );
在生产应用中,您可以重置单个界面部分,而不是重新加载整个页面。
在此阶段,用户可以绘制多个红action框,移除单个选择,从当前页面清除所有红action,并精确选择哪些页面应接收所选的红action区域。
## 生成红action后的PDF
当前,红action框仅存在于浏览器预览中。要创建最终文档,我们需要将这些位置应用到实际的PDF页面上。
使用PDF-lib加载原始PDF。
async function generateRedactedPdf() {
const pdfDoc = await PDFLib.PDFDocument.load( pdfBytes.slice(0) );
const pages = pdfDoc.getPages();
const sourceRedactions = redactions[currentPage] || [];
const targetPages = getTargetPages();
}
接下来,遍历所选页面。
targetPages.forEach(pageNumber => {
const page = pages[pageNumber - 1];
applyPageRedactions( page, sourceRedactions );
});
由于浏览器预览和 PDF 页面可能具有不同的尺寸,因此在应用每个矩形之前必须对其进行缩放。
function applyPageRedactions( page, pageRedactions ) {
const { width: pdfWidth, height: pdfHeight } = page.getSize();
const canvas = document.getElementById( "pdfCanvas" );
const scaleX = pdfWidth / canvas.width;
const scaleY = pdfHeight / canvas.height;
pageRedactions.forEach( redaction => {
const x = redaction.x * scaleX;
const width = redaction.width * scaleX;
const height = redaction.height * scaleY;
const y = pdfHeight - ( redaction.y + redaction.height ) * scaleY;
page.drawRectangle({
x, y, width, height,
color: PDFLib.rgb(0, 0, 0)
});
} );
}
在此过程中,黑色矩形会被写入生成的 PDF 页面内容中,而不是仅作为浏览器预览元素存在。
最后,保存处理后的文档。
const outputBytes = await pdfDoc.save();
const outputBlob = new Blob( [outputBytes], { type: "application/pdf" } );
showFinalPreview(outputBlob);
这一区别非常重要,因为仅凭视觉效果不应作为隐藏内容已被安全删除的证明。
## 预览和重命名最终的 PDF
在下载文档之前,用户应能够查看处理后的页面。
最终预览有助于确认每个选定区域是否出现在预期位置。
从处理后的文件中创建一个新的 PDF.js 文档。
async function showFinalPreview(blob) {
const bytes = await blob.arrayBuffer();
const finalPdf = await pdfjsLib .getDocument({ data: bytes }) .promise;
renderFinalPage( finalPdf, 1 );
}
预览可以使用我们之前采用的相同页面导航方法。
let finalPage = 1;
nextFinalPage.addEventListener( "click", async () => {
if ( finalPage >= finalPdf.numPages ) { return; }
finalPage++;
await renderFinalPage( finalPdf, finalPage );
} );
用户可以在保存文件之前浏览生成的 PDF 并直观检查已应用的区域。
该工具还允许用户更改输出文件名。
例如,自动生成的文件名可能是:
combined-images (12)_redacted.pdf
创建一个可编辑的文件名输入框。
<input id="outputFilename" type="text" value="document_redacted.pdf">
在下载之前,请确保文件名以 .pdf 结尾。
function getOutputFilename() {
let filename = outputFilename.value.trim();
if ( !filename .toLowerCase() .endsWith(".pdf") ) {
filename += ".pdf";
}
return filename;
}
允许更改文件名在用户处理同一文档的多个版本时非常有用。
## 下载最终 PDF
最终结果部分会显示处理后文档的基本信息。
在此项目中,用户可以在下载前查看文件名、总页数和最终文件大小。
以兆字节为单位计算文件大小。
function formatFileSize(bytes) {
return ( bytes / 1024 / 1024 ).toFixed(2) + " MB";
}
更新结果信息。
filePageCount.textContent = Total Pages: ${pdfDocument.numPages};
fileSize.textContent = File Size: ${ formatFileSize( outputBlob.size ) };
要下载文档,请创建临时对象 URL。
downloadButton.addEventListener( "click", () => {
const url = URL.createObjectURL( outputBlob );
const link = document.createElement("a");
link.href = url;
link.download = getOutputFilename();
link.click();
URL.revokeObjectURL(url);
} );
浏览器将使用用户选择的文件名下载生成的 PDF。
下载完成后,用户可以点击“重新开始”以清除现有文档并处理另一个 PDF。
function resetTool() {
pdfDocument = null;
pdfBytes = null;
redactions = {};
currentPage = 1;
pdfInput.value = "";
showUploadSection();
}
现在完整的处理工作流程已连接:用户可以在页面上标记区域,应用这些选择,预览结果,重命名生成的文件,查看其详细信息,并下载处理后的 PDF。
## 演示:PDF 涂黑工具的工作原理
现在完整的涂黑工作流程已连接,让我们从上传到下载逐步演示该应用。
### 第 1 步:上传 PDF 文档
用户通过拖放区域或点击“选择 PDF”按钮开始上传 PDF。
浏览器会验证文件,将文档读入内存,并准备进行本地处理。此工作流程不需要后端服务器。
### 第 2 步:预览和导航 PDF
文件加载后,当前 PDF 页面会显示在预览区域中。
页面导航控件允许用户在文档中前后翻页。当前页码和总页数会显示在预览下方。
用户可以先查看文档,然后导航到包含需要涂黑信息的页面。
### 第 3 步:配置涂黑
涂黑设置会显示在 PDF 预览旁边。
用户通过点击并拖动鼠标在敏感区域上直接绘制矩形来选择区域。所选区域会以深色覆盖层显示,使选择的位置易于验证。
页面应用控件还允许用户选择是否将相同的涂黑位置应用到当前页面、所有页面或特定页面编号。
当相同字段在多个页面上以一致的位置出现时,这会很有帮助。
### 第 4 步:管理多个涂黑区域
单个页面可能包含多个需要涂黑的区域。
例如,用户可能选择页面顶部附近的一个名称,以及文档下方的另一项信息。
每个选择会分别显示为“涂黑 #1”、“涂黑 #2”等。
单独的删除控件允许用户删除一个选择而不影响其他选择。“清除本页所有涂黑”选项会从当前活动页面删除所有涂黑区域。
这使用户有机会在处理 PDF 之前更正所选内容。
### 第 5 步:应用并最终确定 PDF
当红action区域和目标页面准备就绪后,用户点击 **应用并最终确定**。
该应用会将浏览器选择的坐标转换为 PDF 页面坐标,并将不透明区域应用到所选页面,同时生成处理后的文档。
如果上传了错误的 PDF 或用户希望重新开始,**重新开始**按钮将重置当前工作流程。
### 第 6 步:预览处理后的 PDF
处理完成后,生成的 PDF 会显示在新的预览区域中。
用户可以浏览文档并直观确认所选区域是否出现在预期位置。
此审查阶段非常重要。一个微小的坐标或页面选择错误可能会导致信息在其他页面上仍然可见。
### 第 7 步:重命名 PDF
在下载之前,用户可以编辑生成的文件名。
该应用可能会自动在原始文件名后添加 `_redacted`,但用户可以将其替换为更符合其文档工作流程的名称。
customer-record_redacted.pdf
或:
public-report-copy.pdf
### 第 8 步:查看文件详情并下载
最后一部分会显示输出文件名、页数和生成文件的大小。
在查看这些信息后,用户点击 **下载** 将 PDF 保存到本地。
浏览器会直接从处理后的文档数据创建下载。
### 第 9 步:使用另一个 PDF 重新开始
在下载文件后,用户可以点击 **重新开始**。
该应用会清除当前 PDF、页面预览、存储的坐标和生成结果,然后返回到上传界面。
用户之后可以处理另一个文档,而无需手动刷新浏览器。
## 如何验证红action后的 PDF
在预览中看起来正确的文档在共享之前仍应进行检查。
首先,浏览每一页受影响的页面,确认每个预期区域都被完全覆盖。特别注意跨多页的红action,因为页面布局可能并不总是完全一致。
该应用可以在最终确定之前执行一个简单的检查,确认红action区域是否存在。
const totalRedactions = Object.values(redactions) .reduce( (total, items) => total + items.length, 0 );
if (totalRedactions === 0) {
alert( "未添加任何红action区域。" );
return;
}
您还应验证每个选定的目标页面都存在。
const validPages = targetPages.every(page =>
page >= 1 &&
page <= pdfDocument.numPages
);
对于像此处所示的基于矩形的实现这样的视觉遮罩工作流程,请记住,一个不透明的矩形本身并不能证明底层 PDF 内容已被安全删除。
如果文档包含受法律保护、高度机密或合规敏感的信息,请使用基于标准的红action和清理流程,删除底层内容对象,然后在共享前测试结果,确保文本选择、可搜索性、注释、元数据和其他可恢复内容已被清除。
在红action项目中,这种验证区分尤其重要。
## 性能优化技巧
PDF 红action 本身看似简单,但处理大型文件时,页面渲染和文档生成会显著消耗浏览器内存。
避免同时以最高分辨率渲染所有 PDF 页面。当用户导航到某页时,仅渲染当前页面。
await renderPage(currentPage);
将红action 坐标存储为小型 JavaScript 对象,而非保存完整的画布图像。
redactions[currentPage].push({
x, y, width, height
});
生成输出时,仅修改需要处理的页面。
for ( const pageNumber of targetPages ) {
const page = pages[pageNumber - 1];
applyPageRedactions( page, sourceRedactions );
}
使用后应释放临时对象 URL。
URL.revokeObjectURL(url);
这些选择可保持界面响应性,减少不必要的内存使用,尤其在处理长报告或多页商业文档时效果显著。
## 重要注意事项和常见错误
红action 界面最常见的错误是坐标转换错误。
画布预览和实际 PDF 页面可能具有不同尺寸。直接将浏览器坐标应用到 PDF 上可能导致矩形偏离目标内容。
始终先计算缩放比例值。
const scaleX = pdfWidth / canvas.width;
const scaleY = pdfHeight / canvas.height;
另一个常见错误是忘记 PDF 和画布的 Y 坐标可能使用不同的原点。
const pdfY = pdfHeight - ( redaction.y + redaction.height ) * scaleY;
用户在将一个选择应用到所有页面时也应格外小心。文档中某个位置可能始终显示页眉,但其他页面布局可能发生变化。始终预览处理后的结果。
处理前应验证自定义页面范围。
targetPages = targetPages.filter(page =>
Number.isInteger(page) &&
page >= 1 &&
page <= pdfDocument.numPages
);
最后,除非实现确实从 PDF 结构中移除了底层文本、图像、注释和相关内容,否则不要将视觉上覆盖的区域描述为已安全删除。
对于简单的浏览器项目,不透明遮罩能很好地展示坐标映射和 PDF 修改过程。处理敏感信息的生产级红action 系统需要更强的内容删除和输出净化逻辑。
## 结论
在本教程中,你使用 JavaScript 构建了一个基于浏览器的 PDF 红action 界面。
你学习了如何上传 PDF、使用 PDF.js 渲染文档页面、在页面间导航、绘制红action 区域、存储矩形坐标、管理多选、选择目标页面、将画布坐标转换为 PDF 坐标、生成处理后的 PDF、预览结果、重命名输出文件并本地下载。
你还了解了一个重要的安全区别:用不透明矩形视觉覆盖内容并不等同于永久删除底层 PDF 内容。这种差异在从学习项目过渡到生产级红action 系统时尤为重要。
你可以使用 PDF 红action 工具探索基于浏览器的工作流程。
一旦你理解了坐标和页面处理流程,就可以通过添加可搜索文本检测、自动模式识别、注释清理、元数据净化、红action验证或真正的内容移除流水线来扩展项目功能。
相同的坐标映射概念也可以复用在构建PDF注释、签名、高亮、裁剪和文档审查工具时。
AllInOneTools.net 创始人 —— 开发简单、免费、无需登录的网络工具,解决日常问题。我专注于实用工具、SEO、效率提升以及公开发布有用的软件。在构建和运营 AllInOneTools 的过程中,我以真实经验进行写作。
如果你读到这里,请感谢作者以表达你的支持。说声谢谢
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发者。立即开始
ADVERTISEMENT