觉得太长?直接看一下结尾的总结?
有的时候我们希望自己放在网站的 PDF 内容,只可以被用户查看,但不想让其下载。前端该如何实现呢?
(这里介绍的技术所针对的用户是 “普通用户” 的 “普通操作”,换言之你很难找到一种技术阻止一个拥有丰富计算机知识的人下载你的 pdf 文档,同时也阻止不了用户进行手动地截屏操作。
一、下载途径分析
通常如果想在页面中嵌入 pdf,最简单的方式应该是
<iframe src="xxx.pdf" />
大致效果如下图,我们可以看出:
- 浏览器提供了一个 pdf 菜单栏,进而下载功能
- 右键菜单提供 “存储为…” 功能
- 此外多处可以进行打印操作
通过以上简单分析,我们发现,这些下载功能大多数都是浏览器提供的!那么,我们能否找到一种不通过浏览器来渲染 pdf 文档的方式呢?
二、mozilla/pdf.js
pdf.js 是一个 H5 的 pdf 文档浏览工具,由 Mozilla 支持以及通过社区进行维护更新。更加详细的文档可以参考其 github 工程。
pdf.js 的渲染方式是通过画布进行渲染,我们想通过 JavaScript 对 canvas 元素进行各种操作就方便多了。这里我基于官方示例(需翻墙),逐条看一下如何一步步禁止下载操作。
官方示例文档最简单的示例包含 html、js 和 css,分别如下(略作修改):
html 部分 (example.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://mozilla.github.io/pdf.js/build/pdf.js"></script>
<script src="example.js"></script>
<link rel="stylesheet" type="text/css" href="example.css">
<title>PDF 禁止下载演示</title>
</head>
<body>
<div>
<div style="margin: 0 auto;">
<button id='btn-pre-page' onclick="goToPrePage()">上一页</button>
<button id='btn-next-page' onclick="goToNextPage()">下一页</button>
</div>
<canvas id="the-canvas"/>
</div>
</body>
</html>
css 部分 (example.css)
#the-canvas {
border: 1px solid black;
direction: ltr;
}
javascript 部分 (example.js)
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
// 注意跨域问题
var url = 'http://videocrawler.ws.126.net/阿里开发规范.pdf';
// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window['pdfjs-dist/build/pdf'];
// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.5.207/pdf.worker.min.js';
var loadingTask = pdfjsLib.getDocument(url);
var pdfHandler = null;
// global page number control
var pageNumber = 1;
var totalNumber = -1;
// Asynchronous download of PDF
loadingTask.promise.then(
// success callback
function(pdf) {
console.log('PDF loaded');
pdfHandler = pdf;
totalNumber = pdf.numPages;
setPage(1);
},
// failure callback
function (reason) {
// PDF loading error
console.error(reason);
}
);
function setPage(pageNumber) {
pdfHandler.getPage(pageNumber).then(
function(page) {
console.log('Page loaded: ' + pageNumber);
var scale = 1.0;
var viewport = page.getViewport({scale: scale});
// Prepare canvas using PDF page dimensions
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
console.log('Page rendered');
});
}
);
}
function goToPrePage() {
if (pageNumber == 1) {
alert("这是第一页!");
} else {
pageNumber--;
setPage(pageNumber);
}
}
function goToNextPage() {
if (pageNumber == totalNumber) {
alert("这是最后一页!");
} else {
pageNumber = pageNumber + 1;
setPage(pageNumber);
}
}
将上面的 example.html、example.css 和 example.js 文件放在本地同一个目录下,即可直接本地代开 example.html,效果如下图
2.1 禁止 pdf 菜单栏以及附加的下载功能
上面的示例中已经没有菜单栏了~
2.2 禁止右键菜单提供 “存储为…” 功能
可以通过监听 contextmenu
事件,调用 preventDefault
方法禁止右键点击,在上面的 js 文件结尾加一行
document.addEventListener('contextmenu', event => event.preventDefault());
即可阻止所有右键。
2.3 禁止打印为 pdf 功能
这里需要利用 css 的 @media print
属性。这个属性的本来作用,是为了优化打印页面的样式。对于我们不想打印的部分,只需要设置 display: none
即可。针对这里的示例,即添加如下 css 语句
@media print {
#the-canvas {
display: none;
}
}
加上上述 css 后,点击浏览器的打印页面功能,结果如下图
2.4 进一步隐藏文档地址
对于一些有基本网络知识的用户,可以从浏览器调试工具中的搜索或者 Network 中找到 pdf 的文档地址,如下图
针对这个问题的一个比较简单和取巧的办法是:不要将文档命名为 .pdf 或类似的文件。
总结
- Step 1: 使用 pdf.js 来渲染 pdf 而不是使用 iframe
- Step 2: 通过监听
contextmenu
事件,调用preventDefault
方法禁止右键点击 - Step 3: 通过 css 属性
@media print
来禁止浏览器打印页面 - Step 4: 修改资源名称
再次声明,上述技巧只能防止普通用户下载 pdf,却无法防止爬虫工程师的破解!
另外本人并非前端工程师,如有纰漏大家可以评论指出~
参考
最后分享一个小技巧
如果你想让一个 pdf 文档隐藏浏览器提供的菜单栏,只需要在链接结尾加上 #toolbar=0
即可,例如 http://videocrawler.ws.126.net/阿里开发规范.pdf#toolbar=0