前端解決方案:實現網頁截圖并導出PDF功能
在前端開發中,我們經常會遇到需要將網頁內容導出為PDF的需求。本文將以一個準考證預覽和導出的例子,帶你一步步實現這個功能。我們會處理包括跨域圖片、Canvas繪圖、PDF生成等多個技術要點。
一、基礎環境搭建
首先,我們需要搭建一個基礎的HTML結構,并引入必要的依賴。
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>網頁截圖導出PDF示例</title></head><body><!-- 引入依賴 --><script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script></body>
</html>
這里我們引入了兩個重要的庫:
- html2canvas:用于將網頁內容轉換為canvas圖像
- jsPDF:用于生成PDF文件
二、創建頁面內容
接下來,我們創建一個簡單的準考證預覽界面:
<div id="ticket"><h1>準考證</h1><table border="1"><tr><th>考生姓名</th><th>張三貓</th></tr><tr><td>照片</td><td><img src="https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg" alt="" /></td></tr></table>
</div><button onclick="fetchImage()">檢測圖片是否支持跨域</button>
<button onclick="newImage()">圖片轉base64</button>
<button onclick="exportToPDF()">導出PDF準考證</button>
三、處理跨域圖片問題
在處理外部圖片時,我們首先需要解決跨域問題。
運維:需設置圖片允許跨域訪問,以阿里云 OSS 跨域規則配置為例。
前端:先檢測圖片是否支持跨域訪問,支持圖片跨域訪問的情況下,再把圖片轉base64。
1. 檢測圖片跨域支持
function fetchImage() {fetch('https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg').then((res) => {console.log('支持跨域', res.type)}).catch((err) => {console.log('不支持跨域', err)})
}
2. 圖片轉Base64
function newImage() {// 創建圖片const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'// 設置跨域img.crossOrigin = 'anonymous'// 監聽圖片加載img.onload = () => {// 創建canvasconst canvas = document.createElement('canvas')// 設置canvas的寬高canvas.width = img.widthcanvas.height = img.height// 獲取canvas的上下文const ctx = canvas.getContext('2d')// 繪制圖片ctx.drawImage(img, 0, 0)// 轉換為base64const base64 = canvas.toDataURL('image/jpeg')console.log('base64轉換成功', base64)}
}
四、圖片處理工具函數
為了確保所有圖片都能正確加載和處理,我們需要兩個重要的工具函數:
1. 轉換圖片為Base64
async function convertImageToBase64(url) {return new Promise((resolve, reject) => {const img = new Image()img.src = urlimg.crossOrigin = 'anonymous'img.onload = () => {const canvas = document.createElement('canvas')canvas.width = img.widthcanvas.height = img.heightconst ctx = canvas.getContext('2d')ctx.drawImage(img, 0, 0)resolve(canvas.toDataURL('image/jpeg'))}img.onerror = () => {console.log('圖片加載失敗')reject(new Error('圖片加載失敗'))}})
}
2. 等待所有圖片加載完成
function waitForImagesLoaded() {return Promise.all(Array.from(document.images).filter((img) => !img.complete).map((img) =>new Promise((resolve) => {img.onload = img.onerror = resolve})))
}
五、實現PDF導出功能
最后,我們來實現核心的PDF導出功能:
async function exportToPDF() {try {// 1. 等待所有圖片加載await waitForImagesLoaded()// 2. 處理頁面中的所有圖片const images = document.querySelectorAll('img')for (const img of images) {try {const base64 = await convertImageToBase64(img.src)img.src = base64} catch (e) {console.error('圖片轉換失敗', e)}}// 3. 將頁面轉換為canvasconst ticket = document.getElementById('ticket')const canvas = await html2canvas(ticket, {scale: 2, // 提高清晰度useCORS: true, // 允許跨域})const imgData = canvas.toDataURL('image/png')// 4. 創建PDF文檔const pdf = new jspdf.jsPDF({orientation: 'portrait', // 豎向unit: 'mm', // 單位:毫米format: 'a4', // A4紙張})// 5. 計算適合的圖片尺寸const pageWidth = pdf.internal.pageSize.getWidth()const pageHeight = pdf.internal.pageSize.getHeight()const imgWidth = pageWidth - 20 // 左右各留10mm邊距const imgHeight = (canvas.height * imgWidth) / canvas.width// 6. 將圖片添加到PDF中pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)// 7. 下載PDF文件pdf.save('張三的準考證.pdf')} catch (error) {console.error('PDF導出失敗', error)}
}
六、完整代碼
將上述所有代碼組合在一起,就構成了一個完整的網頁截圖并導出PDF的功能。
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>網頁截圖導出PDF示例</title></head><body><!-- 引入依賴 --><script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script><div id="ticket"><h1>準考證</h1><table border="1"><tr><th>考生姓名</th><th>張三貓</th></tr><tr><td>照片</td><td><img src="https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg" alt="" /></td></tr></table></div><button onclick="fetchImage()">檢測圖片是否支持跨域</button><button onclick="newImage()">圖片轉base64</button><button onclick="exportToPDF()">導出PDF準考證</button><script>function fetchImage() {fetch('https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg').then((res) => {console.log('支持跨域', res.type)}).catch((err) => {console.log('不支持跨域', err)})}function newImage() {// 創建圖片const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'// 設置跨域img.crossOrigin = 'anonymous'// 監聽圖片加載img.onload = () => {// 創建canvasconst canvas = document.createElement('canvas')// 設置canvas的寬高canvas.width = img.widthcanvas.height = img.height// 獲取canvas的上下文const ctx = canvas.getContext('2d')// 繪制圖片ctx.drawImage(img, 0, 0)// 轉換為base64const base64 = canvas.toDataURL('image/jpeg')console.log('base64轉換成功', base64)}}// 先獲取圖片的base64編碼async function convertImageToBase64(url) {return new Promise((resolve, reject) => {const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'img.crossOrigin = 'anonymous'img.onload = () => {const canvas = document.createElement('canvas')canvas.width = img.widthcanvas.height = img.heightconst ctx = canvas.getContext('2d')console.log(11111, img)ctx.drawImage(img, 0, 0)resolve(canvas.toDataURL('image/jpeg'))}img.onerror = () => {console.log(222222, '圖片加載失敗')}})}// 等待圖片加載function waitForImagesLoaded() {return Promise.all(Array.from(document.images).filter((img) => !img.complete).map((img) =>new Promise((resolve) => {img.onload = img.onerror = resolve}),),)}// 修改導出函數async function exportToPDF() {// 先等待圖片加載await waitForImagesLoaded()// 再處理圖片const images = document.querySelectorAll('img')for (const img of images) {try {const base64 = await convertImageToBase64(img.src)img.src = base64} catch (e) {console.error('圖片轉換失敗', e)}}// 截圖到canvas中const ticket = document.getElementById('ticket')const canvas = await html2canvas(ticket, {scale: 2, // 縮放比例useCORS: true, // 允許跨域})const imgData = canvas.toDataURL('image/png')// 創建pdfconst pdf = new jspdf.jsPDF({orientation: 'portrait', // 方向: 豎屏unit: 'mm', // 單位: 毫米format: 'a4', // 紙張大小: A4})// 獲取pdf的寬高const pageWidth = pdf.internal.pageSize.getWidth()const pageHeight = pdf.internal.pageSize.getHeight()// 計算圖片縮放比例以適應頁面寬度const imgWidth = pageWidth - 20 // 留邊距const imgHeight = (canvas.height * imgWidth) / canvas.width// 添加圖片到pdfpdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)// 下載pdfpdf.save('張三的準考證.pdf')}</script></body>
</html>
七、技術要點總結
-
跨域處理:
- 使用
crossOrigin = 'anonymous'
處理跨域圖片 - 將圖片轉換為Base64格式避免跨域問題
- 使用
-
異步處理:
- 使用 Promise 處理圖片加載
- 使用 async/await 簡化異步代碼
-
Canvas操作:
- 創建Canvas元素
- 設置Canvas尺寸
- 在Canvas中繪制圖片
-
PDF生成:
- 設置PDF屬性(方向、單位、紙張大小)
- 計算圖片在PDF中的合適尺寸
- 添加圖片到PDF并下載
八、注意事項
- 確保服務器端圖片資源允許跨域訪問(設置正確的CORS頭)
- 考慮圖片加載失敗的情況,添加適當的錯誤處理
- 根據實際需求調整PDF的參數(如邊距、縮放比例等)
- 在生產環境中建議使用可靠的CDN或本地托管依賴庫
九、擴展優化
- 添加加載提示
- 支持自定義PDF文件名
- 支持自定義PDF頁面大小和方向
- 添加水印或其他安全標記
- 優化圖片質量和文件大小
希望這篇教程能幫助你理解和實現網頁截圖并導出PDF的功能。如果你有任何問題,歡迎在評論區討論!