Chrome 图片解码与 Image.decode API

简介: 在这篇文章里面,我会对 Chrome 是何时对图片进行解码进行说明,并结合 Chrome 的渲染流水线说明为什么图片解码可能会造成动画卡顿,而新的 Image.decode API 是如何让我们控制图片解码的时机,通过预先解码来避免动画卡顿。 为了让读者更好地了解本文的内容,请阅读我之前的文章 —— [浏览器渲染流水线解析与网页动画性能优化][1]。 ## 图片解码 ### 图片

在这篇文章里面,我会对 Chrome 是何时对图片进行解码进行说明,并结合 Chrome 的渲染流水线说明为什么图片解码可能会造成动画卡顿,而新的 Image.decode API 是如何让我们控制图片解码的时机,通过预先解码来避免动画卡顿。

为了让读者更好地了解本文的内容,请阅读我之前的文章 —— 浏览器渲染流水线解析与网页动画性能优化

图片解码

图片解码与动画

默认的情况下,图片的解码会发生在这个图片所属的 image 元素被光栅化的过程中。而当一个元素处于可见区域,或者非常接近可见区域,浏览器的合成器就会安排元素所在图层的光栅化。合成器会检查该图层是否包含图片,如果有的话会创建对应的图片解码任务交由光栅化线程去执行,在这个过程中,合成器所在的合成线程是前台线程,光栅化线程是后台线程。

光栅化线程的图片解码任务

光栅化线程的图片解码任务

我们从浏览器渲染流水线解析与网页动画性能优化了解到网页动画可以分为合成器动画(也可以称为图层动画)和非合成器动画(也可以称为 DOM 动画),对于合成器动画来说,因为可见区域的绘制允许出现空白,所以合成器在动画每一帧的绘制过程中不需要等待该帧可见区域的光栅化任务(包含图片解码任务)的完成,动画的运行和光栅化是异步的。而对于非合成器动画来说,因为不允许可见区域的绘制出现空白,所以动画的每一帧都需要等待可见区域的光栅化任务的完成才允许提交绘制请求,这相当于动画的运行和光栅化是强制同步的。

我在支付宝的技术分享上也解释过为什么页端通过 rAF/timer 来连续改变元素的 transform,模拟惯性滚动的动画,始终在流畅度上很难达到由浏览器合成器驱动的惯性滚动动画的水平,容易出现卡顿掉帧的情况(旧版的手淘页面就是采用这种方式模拟惯性滚动)。其中一个重要的原因就是图片解码,图片解码是光栅化过程中非常耗时的一个步骤,通常需要花费几十毫秒或者上百毫秒,有的超大图片甚至可能达到几百毫秒,如果动画会被图片解码所阻塞,那么当图片即将出现在可见区域时,掉帧也是必然会发生的事情。

之前也有帮蚂蚁庄园分析过一个弹出面板卡顿的问题,这个弹出面板动画也是一个 rAF/timer 驱动的非合成器动画,面板上包含了一个超大图片的显示,动画过程中当图片即将出现在可见区域时,触发图片解码就造成了几百毫秒的动画卡顿。

图片解码缓存

合成器会通过一个图片解码缓存 ImageDecodeCache 来缓存解码后的图片像素数据和生成的纹理,如果一个图片已经解码并在缓存中时,它再次被绘制时就不需要重复解码。但是解码缓存的大小是有限制的,只能容纳有限的图片(在移动设备的上限通常是 128 兆或者更低),缓存采用 MRU(最近使用)的淘汰策略,如果一个图片已经被移出可见区域并且一段时间没有参与绘制,就有可能会被缓存淘汰,当它重新进入可见区域时,就会触发重解码。

Image.decode API

因为图片解码可能会造成非合成器动画的卡顿,那么最直观的优化想法就是,我能不能先解码图片,解码完成后再把图片加入到 DOM 树里面参与绘制。这种方式在 UI 编程里面也十分常见,应用自己管理一个解码图片的缓存池,如果一个图片需要显示时先请求解码,解码完成后才真正加入到 UI 界面中参与绘制。浏览器新增的 Image.decode API 就是让 Web 也具备相似的能力。

Image.decode 可以让 Web 端请求对这个图片进行提前解码,这个请求被 Blink 发送到合成器,合成器就会生成一个图片解码任务交由光栅化线程去运行。Image.decode 会返回一个 Promise,当光栅化线程完成解码任务后会通知合成器并将结果保存在解码缓存里面,合成器再通知 Blink resolve 这个 Promise,这时 Web 端就能接收到图片解码完成的通知。

ScriptPromise HTMLImageElement::decode(ScriptState* script_state,
                                       ExceptionState& exception_state) {
  return GetImageLoader().Decode(script_state, exception_state);
}

void ImageLoader::DispatchDecodeRequestsIfComplete() {
  ...

  LocalFrame* frame = GetElement()->GetDocument().GetFrame();
  for (auto& request : decode_requests_) {
    ...
    Image* image = GetContent()->GetImage();
    frame->GetChromeClient().RequestDecode(
        frame, image->PaintImageForCurrentFrame(),
        WTF::Bind(&ImageLoader::DecodeRequestFinished,
                  WrapCrossThreadWeakPersistent(this), request->request_id()));
    request->NotifyDecodeDispatched();
  }
}

Image.decode API 在 Chrome 中的部分实现,Blink 向合成器发起解码请求

这个演示视频显示了如何使用 Image.decode API 来避免一个 rAF 动画的突然卡顿,这里是Demo 的代码

function prepareImage() {
  var img = new Image();
  img.src = "nebula.jpg";
  img.decode().then(function() { document.body.appendChild(img); });
}

上面的示例代码中,prepareImage 中请求对 nebula.jpg 进行解码,在解码完成后再把它加入到 DOM 树,这样就规避了 rAF 驱动的时钟指针旋转动画因为图片解码造成的卡顿。

如前所述,合成器的图片解码缓存大小是有限的,而且不必要的内存占用也是不好的行为,所以对所有已经加载的图片都发起解码请求并不是一个良好的使用方式。一个可能的更好做法是:

  1. 当图片加载完成后,需要在可见区域显示或者即将需要显示,这时先发起解码请求,等待解码完成后再加入到 DOM 树中(可以跟图片延迟加载的机制相结合);
  2. 如果一个图片已经不在可见区域很长一段时间,可以考虑从 DOM 树移除,只保留一个大小相同的占位符,等下次再进入可见区域时再重复 1 的操作;

如果读者觉得这篇文章有所帮助,请继续关注专栏和为本文点赞,这样可以帮助我继续创作更多更有价值的文章。

目录
相关文章
|
16天前
|
JSON API 数据格式
随机头像图片[API盒子官方资源库]免费API接口教程
API盒子提供的头像资源接口,包含大量网络公开收集的头像,适合非商业用途。支持POST/GET请求,需提供用户ID、KEY及返回格式类型。返回数据包括状态码和消息内容,支持JSON/TXT格式。更多详情见API盒子官网。
|
14天前
|
API
图片转ASCII图片(像素图,艺术图)免费API接口教程
此API可将指定图片转换为ASCII风格或像素风格图片。支持POST/GET请求,需提供用户ID、KEY及图片等参数,可选设置背景色、文本色、图片宽度、灰度及风格。返回状态码、提示信息及图片地址。示例及详情见官网。
|
16天前
|
JSON API 数据格式
随机壁纸图片[API盒子官方资源库]免费API接口教程
API盒子提供的图片资源接口,含数十万张网络公开图片(非商用)。通过POST或GET请求,需提交用户ID、KEY、返回格式及图片类型等参数。返回数据包括状态码和图片地址或错误信息。 示例ID与KEY共享调用限制,建议使用个人ID与KEY。详情见API文档。
|
21天前
|
Web App开发 人工智能 自然语言处理
WebChat:开源的网页内容增强问答 AI 助手,基于 Chrome 扩展的最佳实践开发,支持自定义 API 和本地大模型
WebChat 是一个基于 Chrome 扩展开发的 AI 助手,能够帮助用户理解和分析当前网页的内容,支持自定义 API 和本地大模型。
53 0
|
4月前
|
API
【Azure API 管理】Azure API Management在设置 Policy时,如何对URL进行解码呢? 使用 HttpUtility.UrlDecode 出错
【Azure API 管理】Azure API Management在设置 Policy时,如何对URL进行解码呢? 使用 HttpUtility.UrlDecode 出错
|
5月前
|
Web App开发 数据可视化 前端开发
Chrome插件实现问题之content-scripts能访问哪些Chrome API
Chrome插件实现问题之content-scripts能访问哪些Chrome API
|
5月前
|
存储 应用服务中间件 API
创建一个随机图片API
通过web服务器(如Apache、Nginx等)访问该文件以获取随机图片。请确保将images数组中的图片路径替换为您自己的图片路径。您还可以扩展该数组,添加更多图片作为可选内容
132 5
|
5月前
|
Web App开发 前端开发
canvas保存图片时,谷歌浏览器Chrome报错【解决方案】Not allowed to navigate top frame to data URL
canvas保存图片时,谷歌浏览器Chrome报错【解决方案】Not allowed to navigate top frame to data URL
149 0
|
6月前
|
数据采集 机器学习/深度学习 搜索推荐
Python第一章(图片与API接口)
Python第一章(图片与API接口)
|
6月前
|
存储 JSON API
随机图片API源码(附新图片数据
含1000+HTTP图片,存储于企业空间,速度媲美新浪。计划扩展更多类别。基础调用:`http:///dm`,JSON格式:`http:///dm?return=json`。返回示例: ```json {
128 0