前端 PDF 预览本领:标签 vs 插件,如何优雅地展示 PDF 文件 ...

打印 上一主题 下一主题

主题 819|帖子 819|积分 2457

媒介

   pdf 作为一种常用的文档格式,信任许多同学都在项目中碰到过需要预览 pdf 文件的情况。其实实现的方式有许多,包罗传统的标签 iframeembed 方式,也可以运用一些插件,比方 pdf.jsvue-pdf 等等,本文将带大家一起探索不同方式的优劣势以及实现方式和应用场景,资助你更好地选择得当本身项目的方案,让你在项目中轻松展示 pdf 文件。
  
一、iframe 标签

<iframe> 标签规定一个内联框架。一个内联框架被用来在当前 HTML 文档中嵌入另一个文档。
1.1 属性

属性描述src在 iframe 中显示的文档的 URL。heightiframe 的高度。widthiframe 的宽度。nameiframe 的名称。seamlessiframe 看起来像是父文档中的一部分。srcdoc规定页面中的 HTML 内容显示在 iframe 中。sandbox对 iframe 的内容定义一系列额外的限定。allow-forms(允许在iframe中提交表单)、allow-same-origin(允许iframe与包含它的页面具有相同的源,这意味着它可以访问与包含页面相同的资源)、allow-scripts(允许在iframe中执行脚本)、allow-top-navigation(允许iframe导航到顶级欣赏上下文,即顶级窗口)scrollingHTML5 不支持。规定是否在 iframe 中显示滚动条。yes、no、autoalignHTML5 不支持。HTML 4.01 已废弃。 规定如何根据周围的元向来对齐iframe。left、right、top、middle、bottomframeborder-HTML5 不支持。规定是否显示 iframe 周围的边框。longdescHTML5 不支持。规定一个页面,该页面包含了有关 iframe 的较长描述。marginheightHTML5 不支持。规定 iframe 的顶部和底部的边距marginwidthHTML5 不支持。规定 iframe 的左侧和右侧的边距。
1.2 代码实现

  1. <template>
  2.   <div>
  3.     <iframe
  4.       src="https://s4.aconvert.com/convert/p3r68-cdx67/agb6w-9i4xt.pdf"
  5.       width="100%"
  6.       height="700"
  7.     />
  8.   </div>
  9. </template>
复制代码
  实现效果
  


二、embed 标签

<embed> 标签定义了一个容器,用来嵌入外部应用大概互动步伐(插件)。
2.1 属性

属性描述height规定嵌入内容的高度。src规定被嵌入内容的 URL。type规定嵌入内容的 MIME 类型。注:MIME = Multipurpose Internet Mail Extensions。width规定嵌入内容的宽度。
2.2 代码实现

  1. <template>
  2.   <div>
  3.     <embed
  4.       src="https://s4.aconvert.com/convert/p3r68-cdx67/abeub-nb503.pdf"
  5.       type="application/pdf"
  6.       width="100%"
  7.       height="700"
  8.     />
  9.   </div>
  10. </template>
复制代码
  实现效果
  


小结

使用 <embed> 或 <iframe> 标签欣赏器会主动调用内置的PDF阅读器插件来显示 pdf 内容。相对于使用插件来显示 pdf 文件有各自的优劣势。
   优势:
  

  • 简单易用: 使用 <embed> 或 <iframe> 标签可以很容易地嵌入 pdf 文件,而且不需要额外的插件或库;
  • 跨平台兼容性: <embed> 或 <iframe> 标签通常具有很好的跨平台兼容性,可以在各种欣赏器和设备上正常显示 pdf 文件;
  • 快速集成: 直接使用标签嵌入 pdf 文件可以快速集成到现有的网页中,不需要额外的学习成本。
   劣势:
  

  • 定制性差: 使用 <embed> 或 <iframe> 标签显示 pdf文件的定制性相对较差,无法轻松实现一些高级功能,如自定义样式、交互等;
  • 功能受限: <embed> 或 <iframe> 标签提供的功能有限,无法实现一些复杂的 pdf 显示需求,如搜索、缩略图预览、无法禁止打印等;
  • 样式控制困难: 使用 <embed> 或 <iframe> 标签嵌入的 pdf文件样式控制相对困难,难以实现与网页样式的统一。

三、vue-pdf 插件

vue-pdf 是一个基于 pdf.js 实现用于在 vue 应用步伐中显示 pdf 文件的库。它提供了一个 vue 组件,可以轻松地将 pdf 文件嵌入到你的应用步伐中,以便用户可以检察和与 pdf 文件交互。
3.1 安装

  1. npm install --save vue-pdf
复制代码
如果有版本题目可以降低版本,推荐安装以下版本:
  1. npm install --save vue-pdf
  2. @4.2.0
复制代码

3.2 引入注册



  • 局部
    1. import pdf from "vue-pdf";
    2. export default {
    3.   components: {
    4.     pdf,
    5.   },
    6. };
    复制代码
  • 全局
    1. import pdf from 'vue-pdf';
    2. Vue.component('pdf', pdf);
    复制代码

3.3 常用的属性及事件



  • Props 属性
       属性描述:srcPDF 文件的 URL。:page要显示的页码。:rotate页面旋转的角度,仅限90度的倍数。 比方:90, 180, 270, 360, …
  • Events 事件
       事件描述@password (updatePassword, reason)updatePassword: 调用以输入 PDF 密码的函数。reason: 函数调用的缘故原由,大概是 ‘NEED_PASSWORD’ 或 ‘INCORRECT_PASSWORD’@progress Number文档加载进度,范围从 0 到 1。@loaded文档加载完成时触发。@page-loaded Number页面加载完成后触发。@num-pages NumberPDF 的总页数。@error Object发生错误时触发。@link-clicked Number点击内部链接时触发。

3.4 底子使用(单页)

  1. <template>  <pdf src="https://s4.aconvert.com/convert/p3r68-cdx67/a0jo0-a0e3v.pdf"></pdf></template><script>import pdf from "vue-pdf";
  2. export default {
  3.   components: {
  4.     pdf,
  5.   },
  6. };
  7. </script>
复制代码
  实现效果
  


3.5 加载本地pdf文件

如果直接将 pdf 文件放在任意一个文件夹中因为路径题目会加载不出来。所以需要将 pdf 放在 public > static 下(如下图),并用 /static/xxx.pdf 的路径方式举行引用( / 即已经代表 public)。

   代码示例
  1. <template>  <pdf src="../../../static/ceshi.pdf"></pdf></template><script>import pdf from "vue-pdf";
  2. export default {
  3.   components: {
  4.     pdf,
  5.   },
  6. };
  7. </script>
复制代码
  实现效果
  


3.6 分页预览(多页)

  1. <template>
  2.   <div id="document">
  3.     <!-- 分页组件 -->
  4.     <div class="component" v-show="schedule === 1">
  5.       <div @click="pageWay('first')" class="headTail">首页</div>
  6.       <div @click="rotateWay('obey')" class="headTail">
  7.         <el-tooltip
  8.           class="item"
  9.           effect="dark"
  10.           content="顺时针旋转"
  11.           placement="top"
  12.         >
  13.           <i style="font-size: 18px" class="el-icon-refresh-right"></i>
  14.         </el-tooltip>
  15.       </div>
  16.       <div
  17.         @click="pageWay('pre')"
  18.         class="fluctuatePage"
  19.         :style="pageNumber === 1 ? 'cursor: not-allowed;' : ''"
  20.       >
  21.         上一页
  22.       </div>
  23.       <div class="pagination">
  24.         <input
  25.           v-model.number="pageNumber"
  26.           type="number"
  27.           class="inputNumber"
  28.           @input="inputEvent()"
  29.         />
  30.         <span> / {{ pageValue }}</span>
  31.       </div>
  32.       <div
  33.         @click="pageWay('next')"
  34.         class="fluctuatePage"
  35.         :style="pageNumber === pageValue ? 'cursor: not-allowed;' : ''"
  36.       >
  37.         下一页
  38.       </div>
  39.       <div @click="rotateWay('contrary')" class="headTail">
  40.         <el-tooltip
  41.           class="item"
  42.           effect="dark"
  43.           content="顺时针旋转"
  44.           placement="top"
  45.         >
  46.           <i style="font-size: 18px" class="el-icon-refresh-left"></i>
  47.         </el-tooltip>
  48.       </div>
  49.       <div @click="pageWay('last')" class="headTail">尾页</div>
  50.     </div>
  51.     <!-- pdf 组件 -->
  52.     <div class="pdfContent">
  53.       <pdf
  54.         :src="pdfUrl"
  55.         ref="pdf"
  56.         v-show="schedule === 1"
  57.         :rotate="pageRotate"
  58.         :page="pageNumber"
  59.         @num-pages="pageValue = $event"
  60.         @progress="schedule = $event"
  61.         @page-loaded="pageNumber = $event"
  62.         @loaded="loadPdfHandler"
  63.         @link-clicked="pageNumber = $event"
  64.         id="pdfID"
  65.       ></pdf>
  66.     </div>
  67.     <!-- loading 组件 -->
  68.     <div class="progress" v-show="schedule !== 1">
  69.       <el-progress
  70.         type="circle"
  71.         :width="60"
  72.         color="#53a7ff"
  73.         :percentage="Math.floor(schedule * 100)"
  74.       ></el-progress>
  75.       <p>{{ loadingTxt }}</p>
  76.     </div>
  77.   </div>
  78. </template>
  79. <script>
  80. import pdf from "vue-pdf";
  81. export default {
  82.   components: {
  83.     pdf,
  84.   },
  85.   data() {
  86.     const loadingText = "加载文件中,文件较大请耐心等待...";
  87.     return {
  88.       remindText: {
  89.         loading: loadingText, // 加载中提示语
  90.         refresh: "若卡住不动,可刷新页面重新加载...", // 刷新提示语
  91.       },
  92.       loadingTxt: loadingText, // 初始加载提示语
  93.       pageRotate: 0, //旋转角度
  94.       pageNumber: 0, // 当前页数
  95.       pageValue: 0, // 总页数
  96.       schedule: 0, // 加载进度
  97.       timerId: "", // 定时器 ID
  98.       pdfUrl: "https://s4.aconvert.com/convert/p3r68-cdx67/a2mdb-i5v70.pdf", // pdf 文件路径
  99.     };
  100.   },
  101.   // 在组件销毁时清除定时器
  102.   destroyed() {
  103.     clearInterval(this.timerId);
  104.   },
  105.   mounted() {
  106.     this.prohibit();
  107.     this.timerId = setInterval(() => {
  108.       // 设置定时器,每隔一段时间切换加载提示语
  109.       this.loadingTxt === this.remindText.refresh
  110.         ? (this.loadingTxt = this.remindText.loading)
  111.         : (this.loadingTxt = this.remindText.refresh);
  112.     }, 4000);
  113.     this.listenerFunction(); // 调用监听滚动条事件的方法
  114.   },
  115.   methods: {
  116.     // 监听输入事件
  117.     inputEvent() {
  118.       // 输入页数大于总页数时,设置为总页数,输入页数小于 1 时,设置为 1
  119.       this.pageNumber = Math.max(1, Math.min(this.pageNumber, this.pageValue));
  120.     },
  121.     // 上一页、下一页、首页、尾页事件
  122.     pageWay(val) {
  123.       if (val === "pre" && this.pageNumber > 1) {
  124.         this.pageNumber--; // 上一页
  125.       } else if (val === "next" && this.pageNumber < this.pageValue) {
  126.         this.pageNumber++; // 下一页
  127.       } else if (val === "first") {
  128.         this.pageNumber = 1; // 首页
  129.       } else if (val === "last" && this.pageNumber < this.pageValue) {
  130.         this.pageNumber = this.pageValue; // 尾页
  131.       }
  132.       this.toTop(); // 滚动到顶部
  133.     },
  134.     // 顺/逆旋转
  135.     rotateWay(val) {
  136.       this.pageRotate += val === "obey" ? 90 : -90;
  137.     },
  138.     // 滚动顶部
  139.     toTop() {
  140.       const container = document.getElementById("document").parentElement;
  141.       container.scrollIntoView({ behavior: "smooth", block: "start" });
  142.     },
  143.     // 监听 pdf 加载完成事件
  144.     loadPdfHandler() {
  145.       this.pageNumber = 1; // 加载 pdf 时,设置当前页数为 1
  146.     },
  147.     // 禁止特定的操作
  148.     prohibit() {
  149.       // 禁用右键菜单
  150.       document.oncontextmenu = function () {
  151.         return false;
  152.       };
  153.       // 禁用按键
  154.       document.onkeydown = function (e) {
  155.         // 定义需要禁用的按键码数组
  156.         const forbiddenKeys = [65, 67, 73, 74, 80, 83, 85, 86, 117, 18, 123];
  157.         // 判断按下的按键是否在禁用数组中,如果是则返回 false 禁用按键
  158.         if (e.ctrlKey && forbiddenKeys.includes(e.keyCode)) {
  159.           return false;
  160.         }
  161.       };
  162.     },
  163.     // 监听滚动条事件
  164.     listenerFunction(e) {
  165.       document
  166.         .getElementById("document")
  167.         .addEventListener("scroll", function (event) {
  168.           // 监听滚动事件
  169.           console.log(event);
  170.         });
  171.     },
  172.   },
  173. };
  174. </script>
  175. <style scoped lang="less">
  176. #document {
  177.   overflow: auto;
  178.   min-height: 100vh;
  179.   width: 100%;
  180.   display: flex;
  181.   position: relative;
  182.   .component {
  183.     user-select: none;
  184.     color: #ffffff;
  185.     position: fixed;
  186.     bottom: 5%;
  187.     left: 50%;
  188.     margin-left: -250px;
  189.     display: flex;
  190.     align-items: center;
  191.     justify-content: space-around;
  192.     background: rgba(0, 0, 0, 0.7);
  193.     border-radius: 30px;
  194.     width: 420px;
  195.     padding: 15px 40px;
  196.     z-index: 99;
  197.     .pagination {
  198.       position: relative;
  199.       top: 1px;
  200.       .inputNumber {
  201.         border-radius: 5px;
  202.         border: 1px solid #8b8b8b;
  203.         width: 36px;
  204.         height: 16px;
  205.         text-align: center;
  206.         background: transparent;
  207.       }
  208.       .inputNumber:focus {
  209.         border: 1px solid #00aeff;
  210.         background: rgba(18, 163, 230, 0.1);
  211.         outline: none;
  212.         transition: 0.2s;
  213.       }
  214.     }
  215.     .headTail {
  216.       border-radius: 50%;
  217.       display: flex;
  218.       align-items: center;
  219.       justify-content: center;
  220.     }
  221.     .fluctuatePage {
  222.       border-radius: 50%;
  223.       display: flex;
  224.       align-items: center;
  225.       justify-content: center;
  226.     }
  227.     .fluctuatePage:hover,
  228.     .headTail:hover {
  229.       transition: 0.3s;
  230.       color: #409eff;
  231.       cursor: pointer;
  232.     }
  233.   }
  234.   .pdfContent {
  235.     width: 100%;
  236.   }
  237.   .progress {
  238.     width: 222px;
  239.     position: absolute;
  240.     top: 50%;
  241.     left: 50%;
  242.     margin-left: -111px;
  243.     text-align: center;
  244.   }
  245.   .progress p {
  246.     color: #199edb;
  247.     font-size: 14px;
  248.   }
  249. }
  250. /*在谷歌下移除input[number]的上下箭头*/
  251. input::-webkit-outer-spin-button,
  252. input::-webkit-inner-spin-button {
  253.   -webkit-appearance: none !important;
  254.   margin: 0;
  255. }
  256. /*在firefox下移除input[number]的上下箭头*/
  257. input[type="number"] {
  258.   -moz-appearance: textfield;
  259. }
  260. </style>
复制代码
  实现效果
  


3.7 打印文件

在 vue-pdf 中,print(dpi, pageList) 方法用于触发打印 pdf 文件的功能。


  • dpi:
    表示打印的分辨率(每英寸的点数)。这个参数用于设置打印时的图像质量,通常可以设置为一个整数值,比方 150、300 等。较高的 dpi 值会产生更清楚的打印效果,但也会增加打印文件的大小。
  • pageList:
    表示要打印的页面列表。这个参数是一个数组,用于指定要打印的页面序号。比方,如果你想打印第 1 页和第 3 页,可以将 pageList 设置为 [1, 3]。如果想打印所有页面,可以使用类似于 [1, 2, 3, ...] 的方式来表示所有页面。
  1. // 设置打印分辨率为 150dpi,打印第1页和第3页
  2. this.$refs.myPdfComponent.print(150, [1, 3]);
复制代码
  1. <template>
  2.   <div>
  3.     <button @click="printPdf">打印 pdf</button>
  4.     <pdf ref="myPdfComponent" :src="pdfSrc"></pdf>
  5.   </div>
  6. </template>
  7. <script>
  8. import pdf from "vue-pdf";
  9. export default {
  10.   components: {
  11.     pdf,
  12.   },
  13.   data() {
  14.     return {
  15.       pdfSrc: "https://s4.aconvert.com/convert/p3r68-cdx67/a1eqq-4rahm.pdf",
  16.     };
  17.   },
  18. };
  19. </script>
复制代码
  实现效果
  


3.8 加密文件

如果你的 pdf 文件是加密的,那么就可以调用 password 方法。


  • updatePassword
    这个函数用于更新 pdf 文件的密码。当用户输入了精确的密码后,可以调用 updatePassword 函数来更新 pdf 文件的密码。
  • reason
    这是一个字符串,表示需要密码的缘故原由。通常会包含一些提示信息,告诉用户为什么需要输入密码才气检察 pdf 文件。
  1. <template>
  2.   <pdf :src="pdfUrl" @password="handlePassword" />
  3. </template>
  4. <script>
  5. import pdf from "vue-pdf";
  6. export default {
  7.   components: {
  8.     pdf,
  9.   },
  10.   data() {
  11.     return {
  12.       pdfUrl: "https://s4.aconvert.com/convert/p3r68-cdx67/a9tag-qf2jw.pdf",
  13.     };
  14.   },
  15.   methods: {
  16.     handlePassword(updatePassword, reason) {
  17.       if (reason === "NEED_PASSWORD") {
  18.         // 这里可以提示用户输入密码,然后使用输入的密码来更新PDF文档
  19.         let password = prompt("请输入PDF文档的密码:", "");
  20.         updatePassword(password);
  21.       }
  22.     },
  23.   },
  24. };
  25. </script>
复制代码
  实现效果
  


3.9 无法显示中文内容

一样平常情况下,是不会出现显示不了中文的题目。但不扫除一些特别的文档,比方票据、合同这类。
  1. <template>
  2.   <pdf :src="pdfUrl" />
  3. </template>
  4. <script>
  5. import pdf from "vue-pdf";
  6. export default {
  7.   components: {
  8.     pdf,
  9.   },
  10.   data() {
  11.     return {
  12.       pdfUrl: "",
  13.     };
  14.   },
  15.   methods: {
  16.     getPdfPort() {
  17.       // 解析 PDF
  18.       const taskData = pdf.createLoadingTask({
  19.         url: res.data,
  20.         cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.5.207/cmaps/",
  21.         cMapPacked: true,
  22.       });
  23.       // 把解析后的地址进行赋值
  24.       this.pdfUrl = taskData;
  25.     },
  26.   },
  27. };
  28. </script>
复制代码
上面代码通过 pdf.createLoadingTask() 方法创建一个任务,该任务负责加载和剖析指定的 pdf 文件。通过这个方法,可以异步地加载 pdf 文件并获取剖析后的数据,以便在页面中显示 pdf 内容。


  • pdf.createLoadingTask() 方法参数
       参数描述url要加载的 pdf 文件的 url。cMapUrl字符映射文件的 url,用于将 pdf 文件中的字符编码映射到可显示的字符。cMapPacked布尔值,指示是否使用压缩的字符映射文件。

拓展

后台返回文档流

许多时间,考虑到安全性、及时性,后台大概不一定会直接返回一个 url 链接,而是会返回一个流文件(如下图所示),这个时间就需要对原始的流文件举行处理后再举行赋值。

   实现代码
  1. <template>
  2.   <div>
  3.     <button @click="pdfWay">预览pdf</button>
  4.     <iframe :src="item.iframeUrl" width="100%" height="550" />
  5.   </div>
  6. </template>
  7. <script>
  8. export default {
  9.   data() {
  10.     return {
  11.       iframeUrl: "",
  12.     };
  13.   },
  14.   methods: {
  15.     pdfWay() {
  16.       previewPort().then((res) => {// 接口方法
  17.         var binaryData = [];
  18.         binaryData.push(res);
  19.         this.iframeUrl = window.URL.createObjectURL(
  20.           new Blob(binaryData, { type: "application/pdf" })
  21.         );
  22.       });
  23.     },
  24.   },
  25. };
  26. </script>
复制代码
在上面的代码中,new Blob(binaryData, { type: "application/pdf" }) 会创建了一个 Blob 二进制对象,binaryData 是一个包含 pdf 文件二进制数据的数组,{ type: "application/pdf" } 指定了这个 Blob 对象的类型为 pdf 文件类型。然后我们调用了 createObjectURL() 方法,该方法担当一个 Blob 对象作为参数,并返回一个包含该 Blob 对象数据的 url。这个 url 是一个临时的、唯一的 url,可以用于在欣赏器中直接访问和展示 Blob 对象的内容。通过这个 url,我们就可以直接在页面上展示 pdf 文件的内容。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表