高德地图 Web 端 Canvas 性能警告问题#

问题背景#

在使用高德地图 JavaScript API 开发应用时,我遇到了浏览器控制台反复显示的 Canvas 性能警告:

1
Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true. See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently

虽然不影响使用,但看着不舒服,所以研究了一下。

我的开发环境是 Vue 3.2 + TypeScript,使用 @amap/amap-jsapi-loader (@2.0) 加载高德地图 API (v2.0)。

这个警告表明高德地图在渲染过程中频繁调用 getImageData() 方法读取Canvas像素数据,但没有设置 willReadFrequently 属性为true,导致浏览器无法为这些频繁的读取操作进行优化。

问题发现#

问题是通过浏览器开发者工具的控制台发现的。从调用栈可以看出,警告发生在地图渲染动画过程中,具体与Canvas2D的绘制和像素读取相关:

1
2
3
4
f.Ce @ maps?callback=___onAPILoaded&v=2.0&key=...
Hn.play @ maps?callback=___onAPILoaded&v=2.0&key=...
f.exec @ maps?callback=___onAPILoaded&v=2.0&key=...
render @ maps?callback=___onAPILoaded&v=2.0&key=...

问题分析#

根据 HTML 规范,当 Canvas 需要频繁读取像素数据时(通过getImageData()方法),应当在创建上下文时设置 willReadFrequently 属性为true:

1
ctx = canvas.getContext('2d', { willReadFrequently: true });

这个设置告诉浏览器对Canvas进行特殊优化,提高像素数据读取的性能。通过浏览器中观察高德地图在内部实现中使用了多个Canvas元素进行渲染,并频繁读取像素数据用于计算和显示,但没有设置这个优化属性。

解决方案#

1. 地图初始化配置#

高德地图的官方文档并没有直接提及如何解决这个性能警告问题。通过其他手段发现 AMap.Map 虽然没有直接文档说明支持 Canvas 配置,但它应该会将这些配置传递给内部的渲染系统
首先,在创建地图实例时添加Canvas配置:

1
2
3
4
5
6
map = new AMap.Map('map-container', {
// 其他配置...
canvas: {
willReadFrequently: true
}
});

2. 添加全局样式优化#

通过添加全局样式改善地图层的触摸行为:

1
2
3
4
5
6
7
8
const style = document.createElement('style');
style.textContent = `
canvas.amap-layer {
touch-action: none;
-ms-touch-action: none;
}
`;
document.head.appendChild(style);

3. 修补Canvas原型方法#

最关键的一步是通过注入自定义脚本,在全局范围内修补HTMLCanvasElement.prototype.getContext方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const script = document.createElement('script');
script.textContent = `
(function() {
// Save original Canvas creation methods
const originalHTMLCanvasElementPrototype = window.HTMLCanvasElement.prototype;
const originalGetContext = originalHTMLCanvasElementPrototype.getContext;

// Replace with method supporting willReadFrequently attribute
originalHTMLCanvasElementPrototype.getContext = function(type, attributes) {
attributes = attributes || {};
if (type === '2d') {
attributes.willReadFrequently = true;
}
return originalGetContext.call(this, type, attributes);
};
})();
`;
document.head.appendChild(script);

这段代码通过闭包保存了原始的getContext方法的同事,确保所有创建的2D Canvas上下文都包含willReadFrequently: true设置。

优化效果#

实施上述解决方案后,浏览器控制台中的Canvas性能警告消失了。这些优化有以下好处:

  1. 提升地图渲染性能:特别是在复杂场景和大量标记的情况下
  2. 减少移动设备电量消耗:优化的渲染过程减少了不必要的计算
  3. 改善用户体验:地图操作更加流畅,尤其是平移和缩放