SVG的image元素load事件提前触发,图片未加载完成就执行后续函数
问题原因
你的问题核心是Safari的渲染时序和微任务执行优先级的冲突:
- 所有Promise的await回调都属于微任务,会在当前JS调用栈清空后、浏览器渲染线程执行前批量执行
- 你修改SVG
<image>的href属性后,浏览器需要在渲染阶段同步资源、完成解码后,导出操作才能读取到正确的内容 - 你原来的代码在微任务队列末尾直接调用
doStuffWithSVG,此时渲染线程还没执行SVG属性的更新同步,虽然DOM属性已经是dataURL,但导出时读取的还是未更新的SVG渲染缓存 - 第二次运行时图片已经缓存,渲染同步耗时极短,刚好能在导出前完成,所以不会报错
- 你额外监听的SVG
<image>load事件存在Safari兼容bug,会在资源实际就绪前触发,进一步放大了时序问题
修复方案
1. 移除冗余的SVG
load事件监听
你已经通过convertUrlToDataUrl提前拿到了合法可用的dataURL,不需要再等待SVG内部image标签的加载事件,这部分逻辑只会引入浏览器兼容问题。修改后的updateImageToDataUrl如下:
const updateImageToDataUrl = async (image) => { const url = image.getAttribute('href') || image.getAttribute('xlink:href'); let dataUrl; try { dataUrl = await convertUrlToDataUrl(url); } catch(e) { dataUrl = 'some backup data URL'; } // 直接更新属性即可,不需要监听load事件 if (image.hasAttribute('xlink:href')) { image.setAttribute('xlink:href', dataUrl); } if (image.hasAttribute('href')) { image.setAttribute('href', dataUrl); } console.log('Image fully updated'); };
2. 等待渲染线程完成SVG更新后再执行导出
在所有属性更新完成后,等待两次requestAnimationFrame,确保浏览器已经完成了一整帧的渲染,SVG的资源更新已经同步到渲染缓存中:
// 原有等待所有转换任务完成的逻辑不变 await Promise.allSettled(imageUpdatingPromises); // 新增兼容Safari的渲染等待逻辑 await new Promise(resolve => { // 两次requestAnimationFrame保证渲染阶段执行完成 requestAnimationFrame(() => requestAnimationFrame(resolve)); // 低版本浏览器也可以替换为 setTimeout(resolve, 0) 效果一致 }); console.log('Starting doStuffWithSVG'); doStuffWithSVG();
原理说明
- 单次
requestAnimationFrame的回调会在浏览器渲染前执行,两次回调间隔了一次完整的渲染流水线,能保证DOM属性的修改已经同步到渲染缓存 - 宏任务(setTimeout)会在微任务队列清空、渲染完成后执行,也能达到同样的效果,兼容性更好
内容的提问来源于stack exchange,提问作者blitz1616
相关产品推荐
相关产品推荐

