整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          基于HTML5 構建的 Web端現代化PDF在線預覽插件-PDF.js

          PDF文件現在在許多企業中常用 - 無論您是要生成銷售報告,交付合同還是發送發票,PDF都是首選的文件類型。PDF.js是由Mozilla編寫的JavaScript庫。由于它使用vanilla JavaScript實現PDF渲染,因此它具有跨瀏覽器兼容性,并且不需要安裝其他插件。在使用PDFJS之前你也可以先了解下原生的PDF<object>對象,本文僅介紹PDFJS。

          官網地址

          https://mozilla.github.io/pdf.js/

          下載和安裝

          官網提供了下載入口,有穩定版和Beta版,我們要在生產環境下使用建議使用穩定版,官網給我們提供了三種獲取PDF.js的方式

          使用說明

          我們可以直接使用cdn服務,也可以將下載的文件引入,我們看一下示例代碼,這里我提供了兩種寫法,在項目運行之前,請確保你的同級目錄下有一個test.pdf文件

          //index.html
          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, initial-scale=1.0">
           <meta http-equiv="X-UA-Compatible" content="ie=edge">
           <script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@2.0.943/build/pdf.min.js"></script>
           <script src='./index.js'></script>
           <title>PDF</title>
          </head>
          <body>
           <canvas id="pdf"></canvas>
          </body>
          </html>
          //index.js
          // var loadingTask = pdfjsLib.getDocument("test.pdf");
          // loadingTask.promise.then(
          // function(pdf) {
          // // 加載第一頁
          // pdf.getPage(1).then(function(page) {
          // var scale = 1;
          // var viewport = page.getViewport(scale);
          // //應用到頁面的canvas上.
          // var canvas = document.getElementById("pdf");
          // var context = canvas.getContext("2d");
          // canvas.height = viewport.height;
          // canvas.width = viewport.width;
          // // 渲染canvas.
          // var renderContext = {
          // canvasContext: context,
          // viewport: viewport
          // };
          // page.render(renderContext).then(function() {
          // console.log("Page rendered!");
          // });
          // });
          // },
          // function(reason) {
          // console.error(reason);
          // }
          // );
          // index.js
          (async () => {
           const loadingTask = pdfjsLib.getDocument("test.pdf");
           const pdf = await loadingTask.promise;
           // 加載第一頁.
           const page = await pdf.getPage(1);
           const scale = 1;
           const viewport = page.getViewport(scale);
           // 應用到頁面的canvas上.
           const canvas = document.getElementById("pdf");
           const context = canvas.getContext("2d");
           canvas.height = viewport.height;
           canvas.width = viewport.width;
           // 渲染canvas.
           const renderContext = {
           canvasContext: context,
           viewport: viewport
           };
           await page.render(renderContext);
          })();
          

          當我們運行項目之后,打開瀏覽器查看,它已經將pdf的內容渲染到了瀏覽器中,且顯示了第一頁,如下圖所示:

          如果就這樣的話遠遠是無法滿足我們使用的,因此我們來看一下它比較高級的用法,或者說簡單的用法,高級的功能。

          使用iframe

          首先我們將我們下載的js包加壓,復制里面的web文件夾,粘貼到你的項目目錄

          然后修改你的index.html代碼,首先注釋掉之前引入的js代碼,然后修改body,如下

          <body>
           <iframe src="test.pdf" style="border: none;" width="100%" height='1000px'></iframe>
          </body>
          

          隨后打開我們的瀏覽器,你會發現一個預覽的窗口

          它繼承了我們常用的功能,比如旋轉、下載、打印、自適應縮放、放大、縮小等,我們只需要使用iframe引入我們的pdf文件即可,其余的全部交給pdf來完成,即可獲得一個實現一個完整的pdf預覽功能。

          PDF.js三個不同層

          • 核心 - PDF的二進制格式在此層中進行解釋。直接使用該層被認為是高級用法。
          • 顯示 - 該層構建在核心層之上,為大多數日常工作提供易于使用的界面。
          • Viewer - 除了提供編程API之外,PDF.js還附帶一個現成的用戶界面,其中包括對搜索,旋轉,縮略圖側邊欄和許多其他內容的支持。

          PDFJS的這三層分開,讓我們很好的來根據業務需求來實現我們想要的部分,其簡單的api讓我們得心應手,總而言之,PDFJS是一個絕佳的PDF預覽解決方案。

          總結

          PDFJS不僅僅支持pdf的二進制文件,同樣還支持base64編碼的pdf,如果在你的項目中需要用到pdf的預覽等功能,無疑它是一種良好的解決方案,當然想要實現相同的功能有許多辦法,我們可以選擇最適合我們需求的,官方還提供了一個完整的演示Demo,如下截圖,如果你覺得本文對你有幫助,請麻煩轉發、點贊加關注吧,后續會分享更多實用有趣的技術!

          端實現文件預覽功能

          ?

          需求:實現一個在線預覽pdf、excel、word、圖片等文件的功能。
          介紹:支持pdf、xlsx、docx、jpg、png、jpeg。
          以下使用Vue3代碼實現所有功能,建議以下的預覽文件標簽可以在外層包裹一層彈窗。

          ?

          圖片預覽

          iframe標簽能夠將另一個HTML頁面嵌入到當前頁面中,我們的圖片也能夠使用iframe標簽來進行展示。

          <iframe  :src="圖片地址"
                   style="z-index: 1000; height:650px; width: 100%; margin: 0 auto"
                   sandbox="allow-scripts allow-top-navigation allow-same-origin allow-popups"
          >
          

          「sandbox」這個屬性如果是單純預覽圖片可以不使用,該屬性對呈現在 iframe 框架中的內容啟用一些額外的限制條件。屬性值可以為空字符串(這種情況下會啟用所有限制),也可以是用空格分隔的一系列指定的字符串。

          • allow-scripts: 允許嵌入的瀏覽上下文運行腳本(但不能創建彈窗)。如果沒有使用該關鍵字,就無法運行腳本。
          • allow-top-navigation: 允許將框架內所加載頁面中的超鏈接導航到父級窗口
          • allow-same-popups: 允許彈窗 (例如 window.open, target="_blank")。如果沒有使用該關鍵字,相應的功能將自動被禁用。
          • allow-same-origin: 如果沒有使用該關鍵字,嵌入的瀏覽上下文將被視為來自一個獨立的源,這將使 same-origin policy 同源檢查失敗。使用了這個屬性,那么當前頁面和iframe打開的頁面視為同源。

          word文檔預覽(docx)

          先下載npm包
          npm i docx-preview --save
          
          <div class="docxRef"></div>
          
          <script>
          import { renderAsync } from 'docx-preview';
          
          function fn() {
          // 這里的res.data是 blob文件流,如果自己的不是blob文件流
          // 可以通過URL.createObjectURL(參數) 參數為File格式,轉換為blob文件流
              let blob = res.data
              let childRef = document.getElementsByClassName('docxRef');
              renderAsync(blob, childRef[0]) //渲染
          }
          fn()
          
          </script>
          

          「blob文件流」

          預覽excel文件(xlsx)

          下載包
          npm install xlsx@0.16.0
          
          <div class="xlsxClass"></div>
          const reader = new FileReader();
          //通過readAsArrayBuffer將blob轉換為ArrayBuffer對
          reader.readAsArrayBuffer(res.data) // 這里的res.data是blob文件流
          reader.onload = (event) => {
            // 讀取ArrayBuffer數據變成Uint8Array
            var data = new Uint8Array(event.target.result);
            // 這里的data里面的類型和后面的type類型要對應
            var workbook = XLSX.read(data, { type: "array" });
            var sheetNames = workbook.SheetNames; // 工作表名稱
            var worksheet = workbook.Sheets[sheetNames[0]];
            // var excelData = XLSX.utils.sheet_to_json(worksheet); //JSON
            let html = XLSX.utils.sheet_to_html(worksheet);
            document.getElementsByClassName('xlsxClass')[0].innerHTML = html
          };
          
          

          pdf預覽

          下載包 npm install pdfjs-dist
          我使用的是npm install pdfjs-dist@2.0.943版本,以下例子使用的是vue3+vite創建的項目
          以下例子通過canvas來渲染pdf
          
          <template>
            <div class="box">
              <div class="tool-bar">
                <div>{{ pdfParams.pageNumber }} / {{ pdfParams.total }}</div>
                <button type="primary" :disabled="pdfParams.pageNumber == pdfParams.total" @click="nextPage">下一頁
                </button>
                <button type="primary" :disabled="pdfParams.pageNumber == 1" @click="prevPage">上一頁</button>
              </div>
              <canvas id="pdf-render"></canvas>
            </div>
          </template>
          
          
          <script setup>
          import { onMounted, ref, reactive } from 'vue'
          const pdfParams = reactive({
            pageNumber: 1, // 當前頁
            total: 0, // 總頁數
          });
          
          // 不要定義為ref或reactive格式,就定義為普通的變量
          let pdfDoc = null;
          // 這里必須使用異步去引用pdf文件,直接去import會報錯,也不知道為什么
          onMounted(async ()=> {
            let pdfjs = await import('pdfjs-dist/build/pdf')
            let pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry')
            pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
            // 此文件位于public/test2.pdf
            let url = ref('/test2.pdf')
            pdfjs.getDocument(url.value).promise.then(doc => {
              pdfDoc = doc
              pdfParams.total = doc.numPages
              getPdfPage(1)
            })
          })
          
          // 加載pdf的某一頁
          const getPdfPage = (number) => {
            pdfDoc.getPage(number).then(page => {
              const viewport = page.getViewport()
              const canvas = document.getElementById('pdf-render')
              const context = canvas.getContext('2d')
              canvas.width = viewport.viewBox[2]
              canvas.height = viewport.viewBox[3]
              viewport.width = viewport.viewBox[2]
              viewport.height = viewport.viewBox[3]
              canvas.style.width = Math.floor(viewport.width) + 'px'
              canvas.style.height = Math.floor(viewport.height) + 'px'
          
              let renderContext = {
                canvasContext: context,
                viewport: viewport,
                // 這里transform的六個參數,使用的是transform中的Matrix(矩陣)
                transform: [1, 0, 0, -1, 0, viewport.height]
              }
              // 進行渲染
              page.render(renderContext)
            })
          }
          // 下一頁功能
          const prevPage = () => {
            if(pdfParams.pageNumber > 1) {
              pdfParams.pageNumber -= 1
            } else {
              pdfParams.pageNumber  = 1
            }
            getPdfPage(pdfParams.pageNumber)
          }
          // 上一頁功能
          const nextPage = () => {
            if(pdfParams.pageNumber < pdfParams.total) {
              pdfParams.pageNumber += 1
            } else {
              pdfParams.pageNumber = pdfParams.total
            }
            getPdfPage(pdfParams.pageNumber)
          }
          </script>
          

          以上pdf代碼引用文章:(54條消息) 前端pdf預覽、pdfjs的使用_pdf.js_無知的小菜雞的博客-CSDN博客
          pdfjs官方代碼:例子 (mozilla.github.io)
          以上代碼看不懂的地方可以查閱官方代碼,大部分都是固定的寫法。

          「以上注意點:」

          • 必須異步引用pdf的文件!??!
          • pdf演示文件位于public/test2.pdf
          • transform: [1, 0, 0, -1, 0, viewport.height],使用了transform中的Matrix(矩陣)
          • 下一頁和上一頁功能都需要重新渲染

          近在實現共享 PDF 文檔的需求,存在主講人這樣一個角色,上傳 PDF 文檔后,通知其它連接中的終端,進行實時同步展示的功能。對于這樣的需求,pdf.js 成功的讓我想起了它。

          PDF 文檔的預覽,總的就是要加載速度快,盡最快的速度完成渲染,呈現給用戶看,不要出現長時間的白屏或 Loading 狀態的現象,另外 PDF 文檔需要支持翻頁等操作。具體看看一步步的實現。

          文檔分片下載速度

          PDF 文檔上傳

          分片上傳文檔,支持秒傳,VUE 支持分片上傳的插件一搜一大把,可以采用 vue-simple-uploader 等,具體如何實現,這里不詳細論述,簡單貼一下秒傳校驗的實現。

          import SparkMD5 from 'spark-md5';
          /**
           * 文件秒傳 MD5 校驗
           * @param file 上傳的文件信息
           */
          md5File(file) {
              const fileReader = new FileReader(),
                  blobSlice = File.prototype.slice,
                  chunkSize = 1024 * 1000,						// 分片大小
                  chunks = Math.floor(file.size / chunkSize),		// 總的分片數量
                  spark = new SparkMD5.ArrayBuffer();				// 三方庫 SparkMD5
              let currentChunk = 0;
              // 加載分片
              const loadNext = () => {
                  const start = currentChunk * chunkSize;
                  let end = file.size;
                  if (currentChunk < chunks - 1) end = start + chunkSize;
                  fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
              }
              // 暫停文件上傳
              file.pause();
            	// 開始校驗文件MD5
              loadNext();
              fileReader.onload = (e) => {
                  spark.append(e.target.result);
                	// 小于總分片, 繼續加載
                  if (currentChunk < chunks - 1) {
                      currentChunk++;
                      loadNext();
                  } else {
                    	// 分片全部加載完成, 生成 MD5
                      const md5 = spark.end();
                    	// 開始服務端校驗 MD5 ( 秒傳 )
                      this.md5Success(md5, file);
                  }
              };
              fileReader.onerror = () => {
                	// 文件讀取出錯, 取消上傳
                  console.log(`文件${file.name}讀取出錯,請檢查該文件`);
                  file.cancel();
              };
          }

          文件秒傳MD5校驗

          PDF 文檔分片

          一個 PDF 文檔,無法一次就預覽所有內容,在有限的可視區域內,只能顯示有限的內容,那我們就獲取能在有限區域內所能展示的那部分內容,以加快 Content Download 的速度,減少用戶第一次打開時的 Loading 時間。

          假設一個 PDF 文檔有 1000 頁,以 5 頁為一片,將該文檔切分成 200 個分片,首次打開默認請求第一個分片,其后根據翻頁來確定是否繼續加載后續的分片信息(如需刷新后仍然展示剛剛所在頁,則需記錄當前頁,根據該值與分片的頁數來確定當前屬于第幾個分片,進而再請求相應分片即可)。

          服務端如何進行分片,則交給服務端就好了,這里就不詳細說了(得注意下中文亂碼的情況)。假設文件信息格式及單個分片的請求地址如下所示:

          /**
           * 文件信息.
           * 在文件上傳后即可拿到.
           */
          const file = {
            	id: 1,
              md5: 'e10adc3949ba59abbe56e057f20f883e',
              total: 1000,
              name: 'VUE 如何實現高性能的 PDF 在線預覽',
              // ...
          }
          
          /**
           * 請求分片.
           * $http 是我針對 axios 的一些常用方法,攔截器等重新封裝后工具類庫
           */
          

          文件信息格式及分片請求地址格式

          請求 PDF 分片

          pdf.js 接口中,getDocument 可用于獲取遠程文檔,返回 PDFDocumentLoadingTask 對象,該對象是一個下載遠程 PDF 文檔的任務,提供了一些監聽方法,可通過 promise 拿到下載完成的 PDF 對象,最終會生成并返回 PDFDocumentProxy 對象,我們接下來所有的操作都是基于該代理類進行的。

          注意在 PDF 文檔中存在有中文時,會出現不顯示的情況,控制臺也會報如下的錯誤提示

          Warning: The CMap "baseUrl" parameter must be specified, ensure that the "cMapUrl" and "cMapPacked" API parameters are provided.

          主要是 PDF 文檔內容存在不支持的字體,暫且引入三方字體來解決該問題

          const url = `https://www.makeit.vip/${md5}-${page}.pdf?id=${fid}&token=${token}`,
          			cMapUrl = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.4.456/cmaps/';
          const 
          promise = PDFJS.getDocument({
          			url,
          			cMapUrl,
          			cMapPacked: true
          		}).promise;

          渲染 PDF 文檔

          因為我實現的是 PDF 一頁的內容,按屏幕尺寸 100% 寬度來顯示的,這樣很容易高度就超出可視范圍了,所以單頁采用滾動形式,多頁則采用按鈕觸發翻頁的形式來展示(為了更好的適配不同尺寸的屏幕,顯示的效果與主講人完全同步),并非采用一字往下無限排,滾動翻頁的形式。

          /**
           * 獲取分片
           * @param fid 文件ID
           * @param md5 文件唯一標識
           * @param token 授權碼
           * @param num 第N個分片
           */
          public getFragmentation(
          	fid: number,
          	md5: string,
          	token: string,
          	num = 0
          ) {
          	// 請求分片, 得到 promise...
          	// ...
          	promise.then((pdf: any) => {
          	  	for (let i = 1; i <= pdf.numPages; i++) {
          	  		pdf.getPage(i).then((page) => {
          	  			const pagination = num * 5 + i;
          	  			this.renderPage(pagination, page);
          	  		});
          	  	}
          	});
          }
          
          
          /**
           * 渲染分頁內容.
           * @param pagination 第N頁
           * @param page 分頁屬性
           */
          protected renderPage(
          	pagination: number,
          	page: any
          ) {
          	// 根據縮放比例, 獲取文檔的可視屬性
          	const viewport = page.getViewport({scale: 1});
          	// 創建用于渲染的Canvas元素
          	const canvas = document.createElement('canvas'),
          		context = canvas.getContext('2d');
          		canvas.width = viewport.width;
          		canvas.height = viewport.height;
          	// 渲染文檔
          	const renderContext = {
          		canvasContext: context,
          		viewport
          	};
          	return page.render(renderContext).promise;
          }

          PDF 文檔翻頁

          在上一頁/下一頁的不斷操作中,1000頁的內容,不斷的進行渲染,難不成要渲染1000個DOM出來?顯然不合理,非得把瀏覽器給搞崩了才肯罷休嗎?幾十個 Canvas 就讓你卡的不要不要的了。具體實現也簡單,保證只顯示 5 個的前提下,根據上一頁或下一頁的操作,增加或刪除相應的 DOM即可。最后貼一下稍微完整一些的代碼(稍微加了一些注釋)。

          1. 獲取分片
          /**
           * 獲取分片
           * @param fid 文件ID
           * @param md5 文件唯一標識
           * @param token 授權碼
           * @param num 第N個分片
           * @param showPage 顯示第N個分片中的第X頁
           * @param speaker 是否為主講人
           * @param render 是否直接渲染
           * @param clear 是否清除原有內容
           */
          public getFragmentation(
          	fid: number,
          	md5: string,
          	token: string,
          	num: number = 0,
          	showPage = 1,
          	speaker?: boolean,
          	render?: boolean,
          	clear?: boolean
          ): Promise<any> {
          	const url = `${process.env.VUE_APP_PROXY_SERVER}/${md5}-${num}.pdf?id=${fid}&token=${token}`,
          		cMapUrl = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.4.456/cmaps/';
          	let promise = PDFJS.getDocument({
          		url,
          		cMapUrl,
          		cMapPacked: true
          	}).promise;
          	promise.then((pdf: any) => {
          		/** 記錄 PDFDocumentProxy 對象, 可避免重復請求已經請求過的分片 */
          		this.files.page = showPage;
          		if (!this.files.pdfs) this.files.pdfs = {} as any;
          		this.files.pdfs[num] = pdf;
          	});
          	/** 是否執行渲染操作 */
          	if (render) {
          		/** 清除 */
          		if (clear) {
          			const documents = this.getContainer() as HTMLDivElement;
          			if (documents) documents.innerHTML = '';
          		}
          		/** 渲染 - 重新賦值 promise, 保證加載完成后的操作時序 */
          		promise = new Promise((resolve) => {
          			promise.then((pdf: any) => {
          				/** 開始遍歷循環 */
          				for (let i = 1; i <= pdf.numPages; i++) {
          					pdf.getPage(i).then((page) => {
          						const pagination = num * 5 + i;
          						if (!this.files.paginations[pagination]) {
          							/** 存儲分頁信息(寬/高/ID等 - ID可用于判斷是否已經渲染及清除DOM操作) */
          							this.files.paginations[pagination] = {} as any;
          						}
          						this.files.paginations[pagination].id = Utils.uid();
          						const renderFinish = this.renderPage(pagination, page, speaker);
          						if (renderFinish) {
          							renderFinish.then(() => {
          								if (i === pdf.numPages) {
          									/**
          									 * 1. 當前為最后一頁或倒數第2頁, 請求下一分片
          									 * 2. 當前為第一頁或第2頁, 請求上一分片
          									 */
          									const left = showPage % 5;
          									if (
          										left === 0 ||
          										left === 4
          									) {
          										/** 回調 - 請求下一個分片 */
          										if (num + 1 <= this.files.total) {
          											this.getFragmentation(
          												fid,
          												md5,
          												token,
          												num + 1,
          												showPage,
          												speaker
          											).then(() => {
          												/**
          												 * 下一個分片請求成功后的處理
          												 * 根據剩余個數, 決定繼續渲染下一分片的1頁還是2頁
          												 */
          												this.getFragmentationSuccess(
          													showPage,
          													left ? 1 : 0
          												);
          											});
          										}
          									} else if (
          										left === 1 ||
          										left === 2
          									) {
          										/** 回調 - 請求上一個分片 */
          										if (num - 1 >= 0) {
          											this.getFragmentation(
          												fid,
          												md5,
          												token,
          												num - 1,
          												showPage,
          												speaker
          											).then(() => {
          												this.getFragmentationSuccess(
          													showPage,
          													left === 2 ? 1 : 0,
          													'prev'
          												);
          											});
          										}
          									}
          									/**
          									 * 渲染完成后返回.
          									 * 因為我需要5頁全部渲染完成后,初始化每一頁上面的涂鴉功能,
          									 * 所以我在最后才返回 Promise, 以保證時序的正確性.
          									 * 若僅僅是展示, 沒有其它功能的話, 無需返回 Promise.
          									 */
          									resolve();
          								}
          							});
          						}
          					});
          				}
          			});
          		});
          	}
          	return promise;
          }
          1. 渲染頁面
          /**
           * 渲染分頁內容.
           * @param pagination 第N頁
           * @param page 分頁屬性
           * @param speaker 是否為主講人
           * @param type 類型(上下頁區分)
           */
          protected renderPage(
          	pagination: number,
          	page: any,
          	speaker = false,
          	type = 'next'
          ): Promise<any> | void {
          	const documents = this.getContainer() as HTMLDivElement;
          	if (documents) {
          		const item = this.createPage(pagination),
          			pageView = page.view,
          			scale = this.getScale(pageView, speaker, item),
          			viewport = page.getViewport({scale});
          		if (this.files.page !== pagination) item.style.display = 'none';
          		/** 這個就是保存一些用得到的屬性, 具體實現代碼就不貼出來了. */
          		this.setPaginationAttrs(
          			pagination,
          			viewport,
          			pageView,
          			scale
          		);
          		/** 創建元素 */
          		const canvas = document.createElement('canvas'),
          			context = canvas.getContext('2d');
          		canvas.width = viewport.width;
          		canvas.height = viewport.height;
          		item.appendChild(canvas);
          		/** 判斷是要插入還是追加元素 */
          		if (type === 'next') documents.appendChild(item);
          		else if (documents.firstChild) documents.insertBefore(item, documents.firstChild);
          		/** 渲染文檔 */
          		const renderContext = {
          			canvasContext: context,
          			viewport
          		};
          		return page.render(renderContext).promise;
          	}
          }
          1. 獲取縮放比
          /**
           * 獲取縮放比.
           * @param origin 文檔原始尺寸
           * @param speaker 是否為主講人(主講人默認以寬為基準)
           * @param wrapper 畫布容器(超出寬度的話, 需要手動設置高度)
           * @param width 待變更元素的寬度
           */
          protected getScale(
          	origin: any,
          	speaker: boolean,
          	wrapper?: HTMLDivElement,
          	width?: number
          ): number {
          	width = width ?? 0;
          	const documents = this.getContainer() as HTMLDivElement;
          	if (documents && !width) width = documents.offsetWidth;
          	if (speaker) {
          		/**
          		 * 左右兩邊增加了一些偏移量(主講人與普通用戶大小不一樣, 這個函數的代碼也沒啥好貼)
          		 * 主講人默認是以屏幕寬度為基準進行文檔縮放的.
          		 */
          		const offsetWidth = this.getOffsetWidth(width);
          		return Math.round(offsetWidth / origin[2] * 100) / 100;
          	} else {
          		/**
          		 * 非主講人為了與主講人顯示內容一致, 默認采用高度為基準, 但有特殊情況,
          		 * 就是高度保證一致的情況下, 寬度卻超出了屏幕的可視區域, 這時候就要將
          		 * 文檔顯示區域所在的容器高度進一步縮短, 保證寬度是在可視區域內, 具體
          		 * 實現就看 getHeightAndScale 這個方法了.
          		 */
          		const heightAndScale = this.getHeightAndScale(documents, origin),
          			scale = heightAndScale.scale;
          		if (wrapper && heightAndScale.height) wrapper.style.height = `${heightAndScale.height}px`;
          		return scale;
          	}
          }
          1. 判定寬度是否超出可視區域
          /**
           * 獲取文檔顯示高度與縮放比例.
           * @param documents
           * @param origin
           */
          protected getHeightAndScale(
          	documents?: HTMLDivElement,
          	origin?: any
          ): {
          	height: number;
          	scale: number;
          } {
          	documents = documents ?? this.getContainer() as HTMLDivElement;
          	let wrapperHeight = 0, lastScale = 0;
          	if (documents) {
          		const size = this.files.speaker,		// 所記錄的主講人的屏幕尺寸
          			width = documents.offsetWidth,		// 當前用戶顯示容器的可視寬度
          			height = documents.offsetHeight;	// 當前用戶顯示容器的可視高度
          		let originWidth;
          		/** 獲取原始文檔寬度 */
          		if (!origin) {
          			const pagination = this.getActivePagination();
          			originWidth = pagination.originWidth;
          		} else {
          			originWidth = origin[2];
          		}
          		/**
          		 * 計算主講人的縮放比.
          		 * 往下出現的 200 / 150 之類的常數, 為設定好的顯示偏移量.
          		 */
          		const speakerRatio = Math.round((size.width - 200) / originWidth * 100) / 100;
          		/** 非主講人默認以高度為基準來計算文檔顯示的縮放比例 */
          		lastScale = Math.round(speakerRatio / (size.height - 150) * (height - 100) * 100) / 100;
          		/** 如果以高度為基準的情況下, 判定寬度是否超出可視區域 */
          		const destWidth = Math.round(originWidth * lastScale * 100) / 100,
          			offsetWidth = width - (this.isSpeaker() ? 200 : 120),
          			diffWidth = destWidth - offsetWidth;
          		if (diffWidth > 0) {
          			/**
          			 * 如果超出可視區域, 重新設定縮放比,
          			 * 文檔內容顯示所在的DIV容器, 將進一步縮小,
          			 * 以保證寬度在正常的可視區域內
          			 */
          			wrapperHeight = (Math.round((offsetWidth * (size.height - 150)) / (size.width - 200) * 100) / 100);
          			lastScale = Math.round(offsetWidth / originWidth * 100) / 100;
          		}
          	}
          	return {
          		height: wrapperHeight,
          		scale: lastScale
          	};
          }

          總結

          翻頁控制的代碼我就不貼出來了,與請求分片中的判定類似??偟膶崿F,沒有太大的難點,理清思路之后就很好實現了,上傳速度快慢先不說,秒傳校驗通過的情況下,基本在 16ms 內完成 Content Download,直至頁面渲染出來,整個過程大概 1s 左右,有個前提是我的實現是等 5 個分頁都渲染完成后又進行了一系列的涂鴉初始化操作后的時間,不做其它處理,只做展示,速度將會更快。


          主站蜘蛛池模板: 精品无码人妻一区二区三区品| 国产激情一区二区三区四区| 无码少妇A片一区二区三区| 午夜视频一区二区三区| 国产高清在线精品一区二区| 国产日韩精品一区二区三区| 精品视频一区二区三区| 色欲综合一区二区三区| 中文字幕乱码人妻一区二区三区| 色一情一乱一伦一区二区三区| 黑巨人与欧美精品一区| 99偷拍视频精品一区二区| 亚洲第一区在线观看| 日韩精品一区二区三区色欲AV | 亚洲av色香蕉一区二区三区 | 日韩精品无码人妻一区二区三区| 亚洲av成人一区二区三区在线观看| 中文字幕一区二区三区永久| 99在线精品一区二区三区| 日本精品一区二区久久久| 麻豆va一区二区三区久久浪| 亚洲日本精品一区二区| 久久国产午夜精品一区二区三区| 国产高清在线精品一区| 国产爆乳无码一区二区麻豆 | 亚洲av成人一区二区三区在线观看| 日韩精品中文字幕无码一区| 国产成人无码精品一区不卡| 国产无码一区二区在线| 一区二区三区四区在线播放| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 精品乱人伦一区二区三区| 伊人久久精品无码av一区| 亚洲av午夜福利精品一区| 少妇人妻精品一区二区三区| 色婷婷一区二区三区四区成人网 | 51视频国产精品一区二区| 亚洲av无码成人影院一区 | 亚洲日韩精品一区二区三区无码| 精品无码一区在线观看| 精品国产一区二区三区久久影院 |