event-throttling
本文回答:
mousemove / move / render等高频事件的节流策略 —— throttle / debounce / RAF 合并。
一、哪些事件是高频
| 事件 | 典型 fire 频率 |
|---|---|
map.on('mousemove', ...) |
每秒 60+ |
map.on('move', ...) |
拖动中每帧 |
map.on('zoom', ...) |
缩放中每帧 |
map.on('render', ...) |
仅地图重绘的帧(动画 / 交互中每帧,静止时不触发) |
eventBus.on('entity:mouseenter', ...) |
每帧 hover 的实体 |
fx.on('render', ...) |
每一帧 |
不高频:click / moveend / zoomend / command:ended / styledata / load。
二、throttle(固定时间间隔)
function throttle(fn, ms) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last > ms) { last = now; fn(...args); }
};
}
map.on('mousemove', throttle(e => {
// 贵的查询:queryRenderedFeatures、HTTP 请求
}, 100));
适合: 节奏稳定的 UI 更新(状态栏、tooltip)。
三、debounce(静止后触发)
function debounce(fn, ms) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), ms);
};
}
map.on('moveend', debounce(() => {
fetchDataInBounds(map.getBounds());
}, 300));
适合: 用户操作停止后的一次性响应(搜索请求、持久化)。
四、requestAnimationFrame 合并
let pending = false;
map.on('mousemove', () => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
// 最多每帧执行一次
update();
pending = false;
});
});
适合: UI 渲染(DOM 更新、Canvas 绘制)。优势:对齐浏览器渲染时序,不卡动画。
五、三种方案对比
| 方案 | 频率 | 适合 |
|---|---|---|
| throttle | 固定(如 100ms) | 查询、面板更新 |
| debounce | 静止后 1 次 | 网络请求、保存 |
| RAF | 每帧最多 1 次 | DOM / Canvas 渲染 |
六、实际场景
鼠标坐标显示
const displayCoord = throttle(e => {
coordHUD.textContent = `${e.lngLat.lng.toFixed(6)}, ${e.lngLat.lat.toFixed(6)}`;
}, 16); // ~60fps,肉眼流畅就够
map.on('mousemove', displayCoord);
视口内数据查询
const fetchInView = debounce(async () => {
const b = map.getBounds();
const data = await api.get(`/features?bbox=${b.toArray()}`);
source.setData(data);
}, 500);
map.on('moveend', fetchInView);
跨组件状态同步
let pending = false;
map.on('move', () => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
syncMiniMap(map.getCenter(), map.getZoom(), map.getBearing());
pending = false;
});
});
七、eventBus 高频 handler
const heavy = throttle(({ entity }) => {
updatePropertyPanel(entity);
}, 100);
mapcadLayer.eventBus.on(VjmapextEvents.EntityMouseEnter, heavy);
enter/leave 也可能每帧 fire(hover 在相邻实体间跳转)。
八、组合:throttle + 跳过重复
let lastId = null;
map.on('mousemove', throttle(e => {
const feats = map.queryRenderedFeatures(e.point, { layers: ['pois'] });
if (feats.length === 0) return;
const id = feats[0].id;
if (id === lastId) return; // 没变,跳过
lastId = id;
tooltip.show(feats[0]);
}, 50));
九、性能采样里 FPS 统计
let frameCount = 0;
let lastTs = performance.now();
map.on('render', () => {
frameCount++;
const now = performance.now();
if (now - lastTs >= 1000) {
console.log(`FPS: ${frameCount}`);
frameCount = 0;
lastTs = now;
}
});
⚠️ 引擎按需渲染,render 只在地图重绘时 fire,静止时不触发——这里统计的是地图重绘帧率(静止时读数趋近 0 并不代表卡),页面 FPS 用独立的 requestAnimationFrame 计数或 DevTools FPS meter。handler 里只 ++ 无 IO → 便宜。不要在 handler 里做 DOM 操作。
十、常见坑
- throttle 内闭包共享太重:每次 handler new 大对象 → GC 压力;提前声明。
- debounce 延迟后组件卸载:组件已销毁但定时器还没跑;卸载时
clearTimeout。 - RAF 嵌 RAF:每一层延迟一帧;避免。
- mousemove 里 queryRenderedFeatures 没节流:每秒 60 次查询,明显卡;必 throttle。
eventBus.off时 handler 是 throttle 包装后的函数:对,要 off 这个包装的引用,不是原函数。
相关
- 交互事件 → event/map-interaction
- 视图事件 → event/map-view
- 清理 → event/cleanup