在Next.js里玩轉pdf預覽

1.背景

? ? ? ? 在項目開發中,pdf預覽是一個很常見的業務。各大公司為了保護自己的知識產權,也會對pdf預覽進行限制,比如:不允許下載、打印,不允許提取文字等等。要想在實現預覽功能的基礎上還要附加這些限制,有很多中可選的方法。本篇文章主要從前端視角談談怎么實現這個業務。

? ? ? ? 在開始講解之前,需要先明確一點:使用純前端的方法是無法完全避免用戶竊取pdf的內容的,只能通過一些配置和腳本增加用戶獲取的難度。更安全的方法是后端對于pdf資源的請求加以限制,或者對pdf增加水印等等。

2.技術棧

? ? ? ? 本篇文章是在Next.js(React框架)的基礎上借助pdf.js三方包演示怎么實現pdf預覽和限制下載的。讀者需要對React,JavaScript語法有基本的了解。

3.實現pdf預覽

3.1. 安裝pdf.js依賴

npm install pdfjs-dist
# 或者
yarn add pdfjs-dist

3.2. 創建 PDF 查看器組件

在?components/PdfViewer.js?中創建組件:

'use client'; // 必須標記為客戶端組件import { useEffect, useRef, useState } from 'react';
import * as pdfjsLib from 'pdfjs-dist';export default function PdfViewer({ pdfUrl }) {const canvasRef = useRef(null);const [numPages, setNumPages] = useState(0);const [currentPage, setCurrentPage] = useState(1);const [scale, setScale] = useState(1.5);// 初始化 PDF.jsuseEffect(() => {pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';}, []);// 加載 PDFuseEffect(() => {if (!pdfUrl) return;const loadPdf = async () => {const loadingTask = pdfjsLib.getDocument(pdfUrl);const pdf = await loadingTask.promise;setNumPages(pdf.numPages);renderPage(pdf, currentPage);};loadPdf().catch(console.error);}, [pdfUrl, currentPage]);// 渲染指定頁面const renderPage = async (pdf, pageNum) => {const page = await pdf.getPage(pageNum);const viewport = page.getViewport({ scale });const canvas = canvasRef.current;const context = canvas.getContext('2d');canvas.height = viewport.height;canvas.width = viewport.width;await page.render({canvasContext: context,viewport: viewport}).promise;};const goToPrevPage = () => {if (currentPage > 1) {setCurrentPage(currentPage - 1);}};const goToNextPage = () => {if (currentPage < numPages) {setCurrentPage(currentPage + 1);}};return (<div className="pdf-viewer"><div className="pdf-controls"><button onClick={goToPrevPage} disabled={currentPage <= 1}>上一頁</button><span>第 {currentPage} 頁 / 共 {numPages} 頁</span><button onClick={goToNextPage} disabled={currentPage >= numPages}>下一頁</button><select value={scale} onChange={(e) => setScale(parseFloat(e.target.value))}><option value="0.5">50%</option><option value="1.0">100%</option><option value="1.5">150%</option><option value="2.0">200%</option></select></div><div className="pdf-canvas-container"><canvas ref={canvasRef} /></div></div>);
}

3.3. 創建樣式文件

在?components/PdfViewer.module.css?中:

.pdf-viewer {width: 100%;max-width: 800px;margin: 0 auto;
}.pdf-controls {display: flex;justify-content: center;align-items: center;gap: 15px;margin-bottom: 20px;
}.pdf-canvas-container {border: 1px solid #ddd;box-shadow: 0 2px 5px rgba(0,0,0,0.1);overflow: auto;max-height: 80vh;
}button {padding: 5px 10px;cursor: pointer;
}button:disabled {opacity: 0.5;cursor: not-allowed;
}select {padding: 5px;
}

3.4. 在頁面中使用組件

在?app/page.js?中:

import PdfViewer from '../components/PdfViewer';
import styles from './page.module.css';export default function Home() {// 可以是本地public文件夾中的PDF或遠程URLconst pdfUrl = '/sample.pdf'; // 確保PDF文件放在public文件夾中return (<main className={styles.main}><h1>PDF 查看器</h1><PdfViewer pdfUrl={pdfUrl} /></main>);
}

3.5.注意事項

  • 在組件的第一行要聲明‘use client’告訴next這是客戶端組件(因為pdf.js調用的是瀏覽器的canvas用來繪制pdf后顯示的,所以必須在瀏覽器環境下才能運行)?

?4.全局事件限制pdf

// 全局事件監聽
useEffect(() => {// 禁用鼠標右鍵const handleContextMenu = (e: MouseEvent) => {e.preventDefault();e.stopPropagation();return false;};// 禁用鍵盤快捷鍵const handleKeyDown = (e: KeyboardEvent) => {if ((e.ctrlKey && (e.key === 's' || e.key === 'S')) ||(e.ctrlKey && (e.key === 'p' || e.key === 'P')) ||(e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'i')) ||e.key === 'F12' ||(e.ctrlKey && e.shiftKey && (e.key === 'C' || e.key === 'c')) ||(e.ctrlKey && e.shiftKey && (e.key === 'J' || e.key === 'j')) ||(e.ctrlKey && (e.key === 'u' || e.key === 'U')) ||(e.ctrlKey && (e.key === 'a' || e.key === 'A')) ||(e.ctrlKey && (e.key === 'c' || e.key === 'C')) ||(e.ctrlKey && (e.key === 'v' || e.key === 'V')) ||(e.ctrlKey && (e.key === 'x' || e.key === 'X'))) {e.preventDefault();e.stopPropagation();return false;}};// 禁用文本選擇const handleSelectStart = (e: Event) => {e.preventDefault();return false;};  // 禁用拖拽const handleDragStart = (e: DragEvent) => {e.preventDefault();return false;};document.addEventListener('contextmenu', handleContextMenu, true);document.addEventListener('keydown', handleKeyDown, true);document.addEventListener('selectstart', handleSelectStart, true);document.addEventListener('dragstart', handleDragStart, true);window.addEventListener('keydown', handleKeyDown, true);return () => {// 組件注銷時,清除事件方式內存泄漏document.removeEventListener('contextmenu', handleContextMenu, true);document.removeEventListener('keydown', handleKeyDown, true);document.removeEventListener('selectstart', handleSelectStart, true);document.removeEventListener('dragstart', handleDragStart, true);window.removeEventListener('keydown', handleKeyDown, true);};}, []);

通過這些全局事件的引用可以很好的限制普通用戶對于下載pdf,但是對于熟練的用戶,還是有很多辦法繞過這些限制的。具體方法感興趣的同學可以自行搜索。

? ? ? ??

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91743.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91743.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91743.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法競賽備賽——【圖論】求最短路徑——Floyd算法

floyd算法 基于動態規劃 應用&#xff1a;求多源最短路 時間復雜度&#xff1a;n^3 dijkstra&#xff1a;不能解決負邊權 floyd&#xff1a;能解決負邊權 不能解決負邊權回路問題 求最短路徑&#xff1a;dijkstra bfs floyd 思路 1.讓任意兩點之間的距離變短&#xff1a;引入…

雙指針(滑動窗口)相關算法題

雙指針算法有時候也叫尺取法或者滑動窗口&#xff0c;是?種優化暴力枚舉策略的手段&#xff1a;當我們發現在兩層 for 循環的暴力枚舉過程中&#xff0c;兩個指針是可以不回退的&#xff0c;此時我們就可以利用兩個指針不回退的性質來優化時間復雜度。因為雙指針算法中&#x…

ScratchCard刮刮卡交互元素的實現

效果展示 刮刮卡是?種常見的網頁交互元素&#xff0c;通過模擬物理世界的刮涂層來揭示下方的內容。這種效果主要依賴于HTML5的 元素來實現。以下是?個基于TypeScript的刮刮卡實現示例&#xff0c;包括配置項、初始化方法和核心的刮開邏輯。下面是展示的效果部分刮開效果&…

【Python LeetCode 專題】熱題 100,重在思路

哈希1. 兩數之和49. 字母異位詞分組128. 最長連續序列雙指針283. 移動零11. 盛最多水的容器15. 三數之和42. 接雨水滑動窗口3. 無重復字符的最長子串438. 找到字符串中所有字母異位詞子串560. 和為 K 的子數組239. 滑動窗口最大值普通數組53. 最大子數組和56. 合并區間189. 輪轉…

openEuler 22.03 LTS Rootless Docker 安裝指南

openEuler 22.03 LTS Rootless Docker 安裝指南 1.創建普通用戶&#xff08;用于無根模式&#xff09; sudo useradd -m docker-user sudo passwd docker-user # 設置密碼 sudo usermod --add-subuids 100000-165535 docker-user sudo usermod --add-subgids 100000-165535 do…

CMake指令:常見內置命令行工具( CMake -E )

目錄 1.簡介 2.核心作用 3.常用命令介紹 3.1.文件操作命令 3.2.系統命令執行 3.3.校驗與哈希 3.4.流程控制與等待 3.5.路徑與文件處理 3.6.歸檔與壓縮 3.7.網絡與下載 3.8.實用工具 4.使用示例 5.與 shell 命令的對比 6.在 CMake 腳本中使用 7.總結 相關鏈接 1…

YOLO融合CAF-YOLO中的ACFM模塊

YOLOv11v10v8使用教程&#xff1a; YOLOv11入門到入土使用教程 YOLOv11改進匯總貼&#xff1a;YOLOv11及自研模型更新匯總 《CAF-YOLO: A Robust Framework for Multi-Scale Lesion Detection in Biomedical Imagery》 一、 模塊介紹 論文鏈接&#xff1a;https://arxiv.org…

Webpack 項目構建優化詳解

1. 相關面試題 1.1. 做過哪些Webpack打包構建優化? 代碼分割:使用 Webpack 的 SplitChunksPlugin 進行代碼分割,將第三方庫、公共代碼與業務代碼分離,提高緩存利用率和加載速度。 Tree Shaking:通過配置 mode: production 或使用 TerserPlugin,移除未引用的代碼,減少…

【深度學習基礎】張量與Tensor的區別?從標量到深度學習的多維世界

目錄引言一、張量&#xff08;Tensor&#xff09;的定義與特性1. 數學中的張量2. 深度學習中的Tensor二、標量&#xff08;Scalar&#xff09;是什么&#xff1f;三、深度學習中的其他核心量1. 向量&#xff08;Vector&#xff09;2. 矩陣&#xff08;Matrix&#xff09;3. 高階…

設計模式一: 模板方法模式 (Template Method Pattern)

模板方法模式是一種行為設計模式&#xff0c;它通過定義一個算法的骨架&#xff0c;而將一些步驟延遲到子類中實現。Template Method 使得子類可以不改變&#xff08;復用&#xff09;一個算法結構 即可重定義&#xff08;override 重寫&#xff09;該算法的某些特定步驟。基本…

Linux驅動學習day24(UART子系統)

一、UART硬件理論1.1 作用及功能UART&#xff1a;通用異步收發傳輸器&#xff0c;簡稱串口。功能&#xff1a;移植u-boot、內核時&#xff0c;主要使用串口查看打印信息。外接各種模塊&#xff0c;比如藍牙GPS模塊。使用UART的時候&#xff0c;要注意1. 波特率 2. 格式&#xf…

NFS共享服務器

目錄 任務要求 思路總結 1.NFS共享服務 服務端 (ip 192.168.48.128) 客戶端 (ip 192.168.48.130) 2.配置autofs自動掛載 任務要求 1.NFS服務器,可以讓PC將網絡中的NFS服務器共享的目錄掛載到本地端的文件系統中,而在本地端的系統中看來&#xff0c;那個遠程主機的目…

FreeRTOS學習筆記之隊列

小編正在學習嵌入式軟件&#xff0c;目前建立了一個交流群&#xff0c;可以留下你的評論&#xff0c;我拉你進群一、簡介隊列是為了任務與任務、任務與中斷之間的通信而準備的&#xff0c;可以在任務與任務、任務與中斷之間消息傳遞&#xff0c;隊列中可以存儲有限的、大小固定…

垃圾收集器-ZGC

前言在Java開發中&#xff0c;垃圾收集器的選擇對系統性能有著致命的影響。Java 8后&#xff0c;雖然G1 GC成為默認&#xff0c;但是它在延遲性控制上仍有限。ZGC作為最新一代高性能低延遲垃圾收集器&#xff0c;解決了CMS和G1在延遲、垃圾堆容量和吞吐量方面的重大突破。本文將…

計算機“十萬個為什么”之跨域

計算機“十萬個為什么”之跨域 本文是計算機“十萬個為什么”系列的第五篇&#xff0c;主要是介紹跨域的相關知識。 作者&#xff1a;無限大 推薦閱讀時間&#xff1a;10 分鐘 一、引言&#xff1a;為什么會有跨域這個“攔路虎”&#xff1f; 想象你正在參觀一座戒備森嚴的城堡…

C語言:20250719筆記

字符數組在C語言中&#xff0c;支持字符串常量&#xff0c;不支持字符串變量。如果想要實現類似的字符串變量&#xff0c;C語言提供了兩種實現方式&#xff1a;字符數組&#xff1a;char name[] “哪吒”&#xff1b;字符指針&#xff1a;char *name "娜吒"&#x…

decltype是什么,什么作用?

基本概念decltype 是 C11 引入的關鍵字&#xff0c;用于推導表達式的類型&#xff0c;且會完整保留類型的細節&#xff08;包括 const、引用 &、指針 * 等&#xff09;。語法:decltype(表達式) 變量名核心特點1.推導依據是表達式本身&#xff0c;而非表達式的結果&#xff…

RPC 與 Feign 的區別筆記

一、基本概念 1.1 RPC&#xff08;Remote Procedure Call&#xff09; 定義&#xff1a;遠程過程調用&#xff0c;允許像調用本地方法一樣調用遠程服務的方法。 本質&#xff1a;跨進程通信&#xff0c;隱藏了底層網絡通信的復雜性。 常見實現&#xff1a; Java 原生 RMIDub…

高防IP能夠防御CC攻擊嗎?它具備哪些顯著優勢?

摘要&#xff1a; 面對日益復雜的網絡攻擊&#xff0c;高防IP作為重要的安全工具&#xff0c;不僅能防御常見的DDoS攻擊&#xff0c;還能有效應對CC攻擊。本文將解析高防IP防御CC攻擊的原理及其核心優勢&#xff0c;幫助讀者了解其在網絡安全中的關鍵作用。一、高防IP能否防御C…

TypeScript 類型注解(一)

一、TypeScript 類型注解1、什么是TpyeScript類型注解- 是否還記得TypeScript的兩個重要特性&#xff1f;- 類型系統、適用于任何規模- 可以說&#xff0c;TS的類型系統是TS最重要的功能&#xff1b;那么什么是類型注解呢&#xff1f;其實就是在聲明變量時&#xff0c;將變量的…