最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用html2canvas+jspdf實(shí)現(xiàn)頁面導(dǎo)出成pdf功能源碼

 更新時(shí)間:2026年01月29日 10:51:19   作者:softshow1026  
jsPDF是一個(gè)強(qiáng)大的JavaScript庫,用于在瀏覽器中直接生成PDF文檔,下面這篇文章主要介紹了利用html2canvas+jspdf實(shí)現(xiàn)頁面導(dǎo)出成pdf功能的相關(guān)資料,文中給了詳細(xì)的代碼示例,需要的朋友可以參考下

封裝一個(gè)好用的頁面導(dǎo)出 PDF 工具 Hook (html2canvas + jspdf)

在最近的一個(gè)項(xiàng)目中,遇到一個(gè)將頁面內(nèi)容(詳情頁)導(dǎo)出為 PDF的需求,但是好像目前沒有直接把dom轉(zhuǎn)成pdf這樣一步到位的技術(shù),所以自己封裝了一個(gè)間接轉(zhuǎn)換的方法,基于 Vue3 + TypeScript 的通用 Hook 封裝,利用 html2canvas 和 jspdf 實(shí)現(xiàn)網(wǎng)頁內(nèi)容導(dǎo)出為 PDF,并解決了 滾動(dòng)截?cái)?、 清晰度不足 以及 自動(dòng)分頁 等常見問題。

一、 技術(shù)選型

  • html2canvas : 將 DOM 元素轉(zhuǎn)換為 Canvas 圖片。
  • jspdf : 將 Canvas 圖片生成 PDF 文件。
  • 封裝 : 使用 Hook 方式封裝,方便復(fù)用。

二、 核心痛點(diǎn)與解決方案

在實(shí)現(xiàn)過程中,我們通常會(huì)遇到以下幾個(gè)坑:

  1. 導(dǎo)出內(nèi)容不全 :如果頁面有滾動(dòng)條,直接截圖只能截取可視區(qū)域。
    • 解法 :在截圖前將 DOM 高度設(shè)置為 auto ,并獲取 scrollHeight 傳遞給 html2canvas 的 windowHeight 參數(shù)。
  2. 圖片模糊 :默認(rèn)截圖出來的 PDF 很模糊。
    • 解法 :設(shè)置 scale: 2 ,提高 Canvas 的像素密度。
  3. PDF 分頁問題 :長圖直接放入 PDF 會(huì)被壓縮變形。
    • 解法 :計(jì)算內(nèi)容高度與 A4 紙高度的比例,通過循環(huán) addPage() 實(shí)現(xiàn)自動(dòng)分頁切割。

三、 源碼實(shí)現(xiàn)

新建文件 useExportPdf.ts,下載依賴 html2canvas 和 jspdf 然后引入:

import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

/**
 * 導(dǎo)出頁面為 PDF
 * @param dom 需要導(dǎo)出的 DOM 元素
 * @param fileName 導(dǎo)出的文件名(不含后綴)
 */
export const useExportPDF = async (dom: HTMLElement, fileName: string) => {
  const element = dom;
  if (!element) {
    console.error('導(dǎo)出失敗,未找到導(dǎo)出元素');
    return;
  }

  // 1. 解決滾動(dòng)截?cái)鄦栴}:獲取元素實(shí)際高度
  const originalHeight = element.scrollHeight;
  // 臨時(shí)設(shè)置高度為 auto,確保能截取到所有內(nèi)容
  const originalStyleHeight = element.style.height;
  element.style.height = 'auto';

  try {
    // 2. 將 DOM 轉(zhuǎn)換為 Canvas
    const canvas = await html2canvas(element, {
      useCORS: true, // 允許跨域圖片
      scale: 2,      // 2倍縮放,解決模糊問題
      scrollY: -window.scrollY, // 修正滾動(dòng)條偏移
      scrollX: 0,
      windowHeight: originalHeight, // 告訴 html2canvas 完整高度
    });

    // 3. 初始化 PDF 實(shí)例
    // p: 縱向, mm: 單位毫米, a4: 紙張格式
    const pdf = new jsPDF('p', 'mm', 'a4');
    
    // A4 紙內(nèi)容寬度(留邊距)
    const imgWidth = 190; 
    // 根據(jù)寬度計(jì)算等比例的高度
    const imgHeight = (canvas.height * imgWidth) / canvas.width;
    
    // 獲取 PDF 頁面可用高度
    const pdfPageHeight = pdf.internal.pageSize.getHeight(); 
    
    // 4. 處理分頁邏輯
    let position = 0;
    
    // 第一頁
    pdf.addImage(canvas, 'PNG', 10, 10 - position, imgWidth, imgHeight);
    position += pdfPageHeight; // 這里簡化處理,按頁面高度分頁

    // 如果內(nèi)容高度超過一頁,循環(huán)添加新頁
    while (position < imgHeight) {
      pdf.addPage();
      // 移動(dòng)圖片位置,實(shí)現(xiàn)視覺上的“接續(xù)”
      // 注意:這里簡單的 position += pageHeight 可能需要根據(jù)實(shí)際情況調(diào)整,
      // 比如減去一些邊距來防止文字被切斷,Demo 中使用了簡化的邏輯。
      pdf.addImage(canvas, 'PNG', 10, 10 - position, imgWidth, imgHeight);
      position += pdfPageHeight;
    }

    // 5. 保存文件
    pdf.save(`${fileName}.pdf`);
  } catch (error) {
    console.error('導(dǎo)出 PDF 異常:', error);
  } finally {
    // 6. 恢復(fù)原始樣式
    element.style.height = originalStyleHeight;
  }
};

四、 如何在組件中使用

在 Vue 組件中,我們只需要獲取到 DOM 引用,然后調(diào)用這個(gè) Hook 即可。

  <a-button type="primary" @click="exportPDF" v-if="disabled"> 導(dǎo)出PDF </a-button>
import { useExportPDF } from '/@/hooks/exportpdf/useExportpdf';
// 導(dǎo)出的 DOM 元素
const pdfContainer = ref<HTMLDivElement>(null); 
// 導(dǎo)出
      const exportPDF = async () => {
        loading.value = true;
        try {
          await useExportPDF(pdfContainer.value, 'xxxxpdf');
          loading.value = false;
        } catch (e) {
          console.log(e);
          loading.value = false;
        }
      };

五、 值得注意的事項(xiàng)

  • html2canvas 將dom元素轉(zhuǎn)成canvas圖片的時(shí)候如果dom元素中有圖片,需要解決跨域問題,這個(gè)一般來講可以在服務(wù)端(圖片源)設(shè)置 :
    圖片的響應(yīng)頭(Response Header)必須包含 CORS 頭,允許你的域名訪問,或者Nginx配置下代理,或者前端解決的話就把圖片轉(zhuǎn)成Base64格式,但是如果圖片比較多,就不建議前端解決了,第一個(gè)因?yàn)檗D(zhuǎn)圖片格式圖片一多就消耗更多時(shí)間,第二個(gè)是因?yàn)檗D(zhuǎn)成Base64格式的圖片會(huì)增加文件大小。
  • 還有一個(gè)就是如果你想導(dǎo)致的內(nèi)容之中,有些是不用導(dǎo)出,或者根據(jù)不同條件來區(qū)分是否導(dǎo)出可以使用 data-html2canvas-ignore這個(gè)屬性,設(shè)置為true就不會(huì)導(dǎo)出這個(gè)元素

    。
  • 最終實(shí)現(xiàn)效果
    需要導(dǎo)出的頁面


    導(dǎo)出的pdf

六、 總結(jié)

通過這個(gè)封裝,我們實(shí)現(xiàn)了一個(gè)輕量級(jí)且功能完備的 PDF 導(dǎo)出工具。它不僅解決了最讓人頭疼的 長頁面截?cái)?問題,還通過 scale 參數(shù)保證了導(dǎo)出的清晰度。

七、 小思考

為啥img標(biāo)簽就能通過圖片url加載圖片,但是把圖片轉(zhuǎn)成Canvas就會(huì)出現(xiàn)跨域問題?
簡單來說就是 標(biāo)簽只是“展示”數(shù)據(jù),而 轉(zhuǎn)成Canvas 需要“讀取”數(shù)據(jù) 。瀏覽器的安全策略(同源策略)就是“看一眼”和“拿走數(shù)據(jù)”的區(qū)別
 標(biāo)簽展示數(shù)據(jù)跟把圖片轉(zhuǎn)成轉(zhuǎn)成Canvas瀏覽器都會(huì)請求圖片,服務(wù)器返回圖片數(shù)據(jù)。區(qū)別在于:
1. 標(biāo)簽加載

  • 請求頭 :瀏覽器發(fā)起請求時(shí), Origin 字段可能不被包含(或者是 null ),或者僅僅作為 Referer 發(fā)送。它通常被視為一個(gè)“簡單請求”。

  • 響應(yīng)頭 :服務(wù)器返回圖片數(shù)據(jù)。通常 不需要 包含 Access-Control-Allow-Origin 等 CORS 相關(guān)頭信息。

  • 結(jié)果 :瀏覽器接收到數(shù)據(jù),渲染引擎直接解碼并在屏幕上繪制像素。JavaScript 無法接觸到這些數(shù)據(jù)。
    2.開啟 CORS 的情況( crossorigin="anonymous" 或 Canvas 請求)
    當(dāng)你為了 Canvas 導(dǎo)出而給圖片添加 crossorigin 屬性,或者使用 JS fetch 請求圖片時(shí):

  • 請求頭 :瀏覽器 強(qiáng)制添加 Origin: https://你的域名.com 字段,明確告訴服務(wù)器是誰在請求。

  • 響應(yīng)頭(關(guān)鍵差別) :

    • 如果服務(wù)器支持跨域 :必須返回 Access-Control-Allow-Origin: * 或 Access-Control-Allow-Origin: https://你的域名.com 。
    • 如果服務(wù)器不支持 :服務(wù)器可能正常返回了圖片數(shù)據(jù)(狀態(tài)碼 200),但 缺少了 CORS 響應(yīng)頭 。
  • 結(jié)果 :

    • 如果 有 CORS 頭:瀏覽器認(rèn)為這份數(shù)據(jù)是“安全”的,允許 Canvas 讀取和導(dǎo)出。
    • 如果 沒有 CORS 頭:雖然數(shù)據(jù)下載下來了,但瀏覽器(網(wǎng)絡(luò)層或渲染層)會(huì) 攔截 這次加載,報(bào)錯(cuò) CORS policy ,圖片甚至可能直接裂開(加載失?。?,更別說畫到 Canvas 上了。

到此這篇關(guān)于利用html2canvas+jspdf實(shí)現(xiàn)頁面導(dǎo)出成pdf功能源碼的文章就介紹到這了,更多相關(guān)html2canvas+jspdf頁面導(dǎo)出pdf內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解JavaScript嚴(yán)格模式的使用方法

    詳解JavaScript嚴(yán)格模式的使用方法

    JavaScript的嚴(yán)格模式(Strict?Mode)是一種在代碼中啟用的特殊模式,用于提供更嚴(yán)格的語法和錯(cuò)誤檢查,以改善代碼質(zhì)量和增強(qiáng)安全性,本文主要介紹JS的嚴(yán)格模式的用法,可以幫助大家避免一些常見的錯(cuò)誤,需要的朋友可以參考下
    2023-05-05
  • js 表格隔行顏色

    js 表格隔行顏色

    表格隔行顏色,就是通過判斷當(dāng)前表格的單數(shù)進(jìn)行判斷。一般asp中應(yīng)用的比較廣泛。
    2009-12-12
  • 如何實(shí)現(xiàn)小程序與小程序之間的跳轉(zhuǎn)

    如何實(shí)現(xiàn)小程序與小程序之間的跳轉(zhuǎn)

    這篇文章主要給大家介紹了關(guān)于如何實(shí)現(xiàn)小程序與小程序之間的跳轉(zhuǎn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • JavaScrip中window.dispatchEvent的原理和使用具體實(shí)例

    JavaScrip中window.dispatchEvent的原理和使用具體實(shí)例

    window.dispatchEvent是JavaScript中用于手動(dòng)觸發(fā)某個(gè)事件的方法,下面這篇文章主要介紹了JavaScrip中window.dispatchEvent的原理和使用的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-04-04
  • JS+CSS實(shí)現(xiàn)可拖動(dòng)的彈出提示框

    JS+CSS實(shí)現(xiàn)可拖動(dòng)的彈出提示框

    這篇文章主要介紹了JS+CSS實(shí)現(xiàn)可拖動(dòng)的彈出提示框,涉及針對(duì)鼠標(biāo)事件及html元素的操作技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-02-02
  • JavaScript動(dòng)態(tài)提示輸入框輸入字?jǐn)?shù)的方法

    JavaScript動(dòng)態(tài)提示輸入框輸入字?jǐn)?shù)的方法

    這篇文章主要介紹了JavaScript動(dòng)態(tài)提示輸入框輸入字?jǐn)?shù)的方法,實(shí)例分析了javascript針對(duì)頁面元素的動(dòng)態(tài)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • JS實(shí)現(xiàn)網(wǎng)頁上隨機(jī)產(chǎn)生超鏈接地址的方法

    JS實(shí)現(xiàn)網(wǎng)頁上隨機(jī)產(chǎn)生超鏈接地址的方法

    這篇文章主要介紹了JS實(shí)現(xiàn)網(wǎng)頁上隨機(jī)產(chǎn)生超鏈接地址的方法,涉及JavaScript隨機(jī)數(shù)的相關(guān)使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11
  • JS實(shí)現(xiàn)批量上傳文件并顯示進(jìn)度功能

    JS實(shí)現(xiàn)批量上傳文件并顯示進(jìn)度功能

    這篇文章主要介紹了JS實(shí)現(xiàn)批量上傳文件并顯示進(jìn)度功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-06-06
  • 微信小程序遍歷Echarts圖表實(shí)現(xiàn)多個(gè)餅圖

    微信小程序遍歷Echarts圖表實(shí)現(xiàn)多個(gè)餅圖

    這篇文章主要介紹了微信小程序遍歷Echarts圖表實(shí)現(xiàn)多個(gè)餅圖,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • JS實(shí)現(xiàn)導(dǎo)航欄樓層特效

    JS實(shí)現(xiàn)導(dǎo)航欄樓層特效

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)導(dǎo)航欄樓層特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-01-01

最新評(píng)論

息烽县| 仙居县| 夏津县| 大余县| 汕尾市| 乌海市| 清苑县| 十堰市| 马鞍山市| 裕民县| 天长市| 宝应县| 广昌县| 虹口区| 石狮市| 桦南县| 卓资县| 措勤县| 康保县| 长顺县| 岑溪市| 宜州市| 平昌县| 磐安县| 隆化县| 江孜县| 孟州市| 茌平县| 三河市| 高密市| 洛浦县| 金门县| 汶川县| 承德市| 临夏县| 新宾| 临清市| 滦平县| 中方县| 博客| 大田县|