JavaScript如何同步从URL图片获取DataURI?解决PDF图片错位问题
解决jsPDF异步加载图片导致位置错误的问题
你遇到的核心问题其实不是“如何同步获取DataURI”——浏览器出于性能和安全限制,根本不支持同步XHR获取Blob类型的响应,所以你没法用同步方式实现。真正要解决的是异步操作的时序控制:你的PDF页面布局逻辑是同步执行的,但图片加载是异步的,导致图片加载完成时,PDF已经生成了错误的页面结构,最终图片位置错乱。
下面给你两种可靠的解决方案,都是通过理顺异步流程来保证图片和PDF布局的同步:
方案一:先批量加载所有图片,再统一生成PDF
这种方式会先把所有需要的图片转换成DataURI并缓存,等全部加载完成后,再执行PDF的布局和图片添加逻辑,完全避免异步时序问题。
第一步:封装Promise版的图片转DataURI函数
function getImageDataUri(url) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open('GET', url, true); request.responseType = 'blob'; request.onload = function() { if (request.status === 200) { const reader = new FileReader(); reader.onload = function(e) { // 替换MIME类型(根据你的需求调整) const imgData = e.target.result.replace("data:application/xml;", "data:application/png;"); resolve(imgData); }; reader.onerror = () => reject(new Error('FileReader读取失败')); reader.readAsDataURL(request.response); } else { reject(new Error(`图片加载失败:${url},状态码:${request.status}`)); } }; request.onerror = () => reject(new Error('XHR请求失败')); request.send(); }); } // 或者用更简洁的fetch替代XHR: // async function getImageDataUri(url) { // const response = await fetch(url); // if (!response.ok) throw new Error(`图片加载失败:${url}`); // const blob = await response.blob(); // return new Promise((resolve, reject) => { // const reader = new FileReader(); // reader.onload = e => resolve(e.target.result.replace("data:application/xml;", "data:application/png;")); // reader.onerror = reject; // reader.readAsDataURL(blob); // }); // }
第二步:修改PDF生成逻辑,先加载所有图片再处理
async function generatePDF() { const doc = new jsPDF(); // 初始化你的PDF状态变量 let g_iPDFGpage = 0; let g_iPDFGimg = 0; let g_fPDFGlastPos = 0; let g_bPDFGisLeft = false; // 1. 预收集所有图片的布局信息和加载任务 const imageJobs = []; $('#imgArea img').each(function() { g_bPDFGisLeft = !g_bPDFGisLeft; let bPDFGposCheck = true; let pos; // 计算当前图片的页面位置 while(bPDFGposCheck) { pos = (g_iPDFGpage === 0) ? doc.autoTable.previous.finalY + 8 + (75 * g_iPDFGimg) : (75 * g_iPDFGimg); if((pos + (75 * (g_iPDFGimg + 1))) >= 300) { doc.addPage(); g_iPDFGimg = 0; g_iPDFGpage++; bPDFGposCheck = true; } else { bPDFGposCheck = false; } } // 保存当前图片的src和布局参数 imageJobs.push({ src: $(this).attr('src'), pos: pos, lastpos: g_fPDFGlastPos, isleft: g_bPDFGisLeft }); g_fPDFGlastPos = pos; if(!g_bPDFGisLeft) g_iPDFGimg++; }); // 2. 等待所有图片加载完成,拿到对应的DataURI const loadedImages = await Promise.all( imageJobs.map(job => getImageDataUri(job.src).then(dataUri => ({...job, dataUri})) ) ); // 3. 统一把图片添加到PDF的正确位置 loadedImages.forEach(job => { const { dataUri, pos, lastpos, isleft } = job; doc.addImage(dataUri, 'PNG', isleft ? 15 : 90, isleft ? pos : lastpos, 75, 75); }); // 导出PDF doc.save('你的文档名称.pdf'); } // 调用生成函数 generatePDF();
方案二:串行处理图片,加载一个添加一个
如果图片数量较多,不想等待全部加载完再处理,可以用async/await串行遍历图片,每加载完一张就立即添加到PDF,再计算下一张的位置:
async function generatePDF() { const doc = new jsPDF(); let g_iPDFGpage = 0; let g_iPDFGimg = 0; let g_fPDFGlastPos = 0; let g_bPDFGisLeft = false; const $images = $('#imgArea img'); // 串行遍历每张图片 for (let i = 0; i < $images.length; i++) { const $img = $($images[i]); g_bPDFGisLeft = !g_bPDFGisLeft; let bPDFGposCheck = true; let pos; // 计算当前图片的页面位置 while(bPDFGposCheck) { pos = (g_iPDFGpage === 0) ? doc.autoTable.previous.finalY + 8 + (75 * g_iPDFGimg) : (75 * g_iPDFGimg); if((pos + (75 * (g_iPDFGimg + 1))) >= 300) { doc.addPage(); g_iPDFGimg = 0; g_iPDFGpage++; bPDFGposCheck = true; } else { bPDFGposCheck = false; } } // 等待当前图片加载完成 const imgData = await getImageDataUri($img.attr('src')); // 立即添加到PDF的正确位置 doc.addImage(imgData, 'PNG', g_bPDFGisLeft ? 15 : 90, g_bPDFGisLeft ? pos : g_fPDFGlastPos, 75, 75); // 更新状态变量 g_fPDFGlastPos = pos; if(!g_bPDFGisLeft) g_iPDFGimg++; } doc.save('你的文档名称.pdf'); } generatePDF();
重要注意事项
- CORS跨域问题:如果图片所在的服务器域名和你的页面域名不同,必须确保服务器配置了正确的CORS响应头(比如
Access-Control-Allow-Origin),否则XHR/fetch请求会被浏览器拦截。 - 错误处理:上述代码可以根据需求添加
try/catch块,处理图片加载失败的情况,比如跳过错误图片或者提示用户。
内容的提问来源于stack exchange,提问作者Gabriel I.
相关产品推荐
相关产品推荐

