You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SVG的image元素load事件提前触发,图片未加载完成就执行后续函数

问题原因

你的问题核心是Safari的渲染时序和微任务执行优先级的冲突:

  1. 所有Promise的await回调都属于微任务,会在当前JS调用栈清空后、浏览器渲染线程执行前批量执行
  2. 你修改SVG <image>的href属性后,浏览器需要在渲染阶段同步资源、完成解码后,导出操作才能读取到正确的内容
  3. 你原来的代码在微任务队列末尾直接调用doStuffWithSVG,此时渲染线程还没执行SVG属性的更新同步,虽然DOM属性已经是dataURL,但导出时读取的还是未更新的SVG渲染缓存
  4. 第二次运行时图片已经缓存,渲染同步耗时极短,刚好能在导出前完成,所以不会报错
  5. 你额外监听的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 22:45:00