跳转到内容
返回

safari截图问题

背景

之前开发 Arknights生涯表 的时候,有一个对结果页进行截图导出的操作,在safari上遇到个奇葩问题,这里记录下(真不愧是当代IE)

现象

需要截图的页面如下

结果页

但是在safari上会出现导出的图里,原本在网页上是图片元素的部分,被截断或者没渲染的情况,如下图

异常case1

异常case2

实现

提出解决方案前,简单说下这里导出操作的实现原理:

  1. 捕获目标dom(这里直接捕获了整个body)
  2. 将获取到的dom转成svg(这利用了dom-to-image
  3. 创建离屏canvas绘制该svg内容
  4. 导出 离屏canvas 为图片保存
    1. 转换的目的是为了在canvas上做 超采样,让导出图清晰一些

也试过其他库成品库(例如 html2canvas),也会有这个问题,甚至还多出一些其他样式不一致问题等,所以自己手搓了

排查记录

实现的核心代码

// ...
export const savePngByCanvas = async (isDown = false) => {
  const svgString = await domtoimage.toSvg(document.body!, {
    bgcolor: getColorScheme() === "dark" ? "black" : "white",
  });

  return new Promise((res, rej) => {
    // 超采样倍率
    const scaleFactor = 3;
    // 创建 Canvas 元素
    const canvas = new OffscreenCanvas(
      document.body.clientWidth * scaleFactor,
      document.body.clientHeight * scaleFactor
    );
    const ctx = canvas.getContext("2d");
    ctx?.scale(scaleFactor, scaleFactor);
    // 创建图像对象
    const img = new Image();
    img.id = "result";
    img.crossOrigin = "anonymous"; // 设置跨域
    img.onload = async e => {
      try {
        if (e.target) {
          ctx!.drawImage(img, 0, 0);
          saveAs(await canvas.convertToBlob(), "Arknights.png");
          res(true);
        }
        res(true);
      } catch (error: any) {
        rej(`图片保存失败${error.toString()}`);
      }
    };

    img.onerror = err => {
      rej(`图片导出失败:渲染失败,${err.toString()}`);
    };

    // 加载 SVG 数据到图像对象
    img.src = svgString;
  });
};
// ...

上述是导出的逻辑,一开始怀疑是图片 没有正确加载没有加载完 就去绘制了,于是将导出操作放到了图片加载完的onload事件里,再观察网络面板以及打log,执行时图片是能正确请求且拿到内容,但safari还是有问题,且每次导出缺失的部分也不一样

接着就怀疑是 drawImage 在两个浏览器的表现行为不一致,因为两个的图形引擎确实不一样,safari是:webkit,chrome的是:Blink + skia

于是多次调试和尝试观察,得到的结论就是safari在绘制时,被绘制的内容包含图片等外部资源,drawimage并不保证画布一定加载完这些资源且绘制完,但chrome一定保证之后的操作拿到的画布一定都是绘制完整的,然后查了查一些资料,找到其他人也有反馈webkit有这个问题:canvas drawImage does not render SVG with embedded images correctly

解决

找到了问题,就可以开始处理了,既然问题是drawimage后画布不一定渲染完外部资源了,那就做一些延迟等待,再进行对canvas的导出操作即可,但是这就是另一个坑,在导出前延迟好几秒在做转换导出,发现还是会偶现图片渲染不完整的情况

最后尝试了各种异步等操作还是不行,然后想着既然是渲染不及时,那多渲染几次,一定能渲染完

于是最终版:检查用户使用Apple生态的设备时,每次渲染3次,每次渲染保证延迟5帧后在调用下一次,在最后一次操作时导出图片就可以完全解决(计算帧率的函数可以用 requestAnimationFrame

最终版 源码



上一篇
站点说明
下一篇
Astro SSG 国际化
×