📌 学习目标
- 掌握添加动画图标的实现方法
- 理解相关API的使用
- 能够独立完成类似功能开发
🎯 核心概念
向地图添加动画图标。
💻 完 整 代 码
代码示例
constmap=newmaplibregl.Map({container:"map",style:"https://demotiles.maplibre.org/style.json",});constsize=200;constpulsingDot={width:size,height:size,data:newUint8Array(size*size*4),onAdd(){constcanvas=document.createElement("canvas");canvas.width=this.width;canvas.height=this.height;this.context=canvas.getContext("2d");},render(){constduration=1000;constt=(performance.now()%duration)/duration;constradius=(size/2)*0.3;constouterRadius=(size/2)*0.7*t+radius;constcontext=this.context;context.clearRect(0,0,this.width,this.height);context.beginPath();context.arc(this.width/2,this.height/2,outerRadius,0,Math.PI*2);context.fillStyle=`rgba(255, 200, 200,${1-t})`;context.fill();context.beginPath();context.arc(this.width/2,this.height/2,radius,0,Math.PI*2);context.fillStyle="rgba(255, 100, 100, 1)";context.strokeStyle="white";context.lineWidth=2+4*(1-t);context.fill();context.stroke();this.data=context.getImageData(0,0,this.width,this.height).data;map.triggerRepaint();returntrue;},};map.on("load",()=>{map.addImage("pulsing-dot",pulsingDot,{pixelRatio:2});map.addSource("points",{type:"geojson",data:{type:"FeatureCollection",features:[{type:"Feature",geometry:{type:"Point",coordinates:[0,0]}}],},});map.addLayer({id:"points",type:"symbol",source:"points",layout:{"icon-image":"pulsing-dot"},});});代码示例
<!DOCTYPEhtml><htmllang="zh-CN"><head><title>向地图添动态图标</title><metaproperty="og:description"content="向地图添加使用 Canvas API 在运行时生成的动画图标。"/><metaproperty="og:created"content="2025-06-25"/><metacharset="utf-8"/><metaname="viewport"content="width=device-width, initial-scale=1"/><linkrel="stylesheet"href="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css"/><scriptsrc="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script><style>body{margin:0;padding:0;}html, body, #map{height:100%;}</style></head><body><divid="map"></div><script>constmap=newmaplibregl.Map({container:"map",style:"https://demotiles.maplibre.org/style.json",});constsize=200;constpulsingDot={width:size,height:size,data:newUint8Array(size*size*4),onAdd(){constcanvas=document.createElement("canvas");canvas.width=this.width;canvas.height=this.height;this.context=canvas.getContext("2d");},render(){constduration=1000;constt=(performance.now()%duration)/duration;constradius=(size/2)*0.3;constouterRadius=(size/2)*0.7*t+radius;constcontext=this.context;context.clearRect(0,0,this.width,this.height);context.beginPath();context.arc(this.width/2,this.height/2,outerRadius,0,Math.PI*2);context.fillStyle=`rgba(255, 200, 200,${1-t})`;context.fill();context.beginPath();context.arc(this.width/2,this.height/2,radius,0,Math.PI*2);context.fillStyle="rgba(255, 100, 100, 1)";context.strokeStyle="white";context.lineWidth=2+4*(1-t);context.fill();context.stroke();this.data=context.getImageData(0,0,this.width,this.height).data;map.triggerRepaint();returntrue;},};map.on("load",()=>{map.addImage("pulsing-dot",pulsingDot,{pixelRatio:2});map.addSource("points",{type:"geojson",data:{type:"FeatureCollection",features:[{type:"Feature",geometry:{type:"Point",coordinates:[0,0]}}],},});map.addLayer({id:"points",type:"symbol",source:"points",layout:{"icon-image":"pulsing-dot"},});});</script></body></html>🔍 代码解析
初始化地图
使用new maplibregl.Map()创建地图实例,配置基本参数。本示例的核心特色是展示如何使用StyleImageInterface接口创建动态动画图标。
关键配置项
- container: 地图容器的 DOM 元素 ID
- style: 使用 MapLibre 官方样式
https://demotiles.maplibre.org/style.json
StyleImageInterface 接口实现
constpulsingDot={width:size,height:size,data:newUint8Array(size*size*4),onAdd(){constcanvas=document.createElement("canvas");canvas.width=this.width;canvas.height=this.height;this.context=canvas.getContext("2d");},render(){constduration=1000;constt=(performance.now()%duration)/duration;// 绘制外圈脉冲效果constradius=(size/2)*0.3;constouterRadius=(size/2)*0.7*t+radius;// 更新图像数据并触发重绘this.data=context.getImageData(0,0,this.width,this.height).data;map.triggerRepaint();returntrue;},};添加动画图标到地图
map.on("load",()=>{map.addImage("pulsing-dot",pulsingDot,{pixelRatio:2});map.addSource("points",{type:"geojson",data:{type:"FeatureCollection",features:[...]}});map.addLayer({id:"points",type:"symbol",source:"points",layout:{"icon-image":"pulsing-dot"}});});⚙️ 参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| container | string | 是 | - | 地图容器元素的 ID |
| style | string/object | 是 | - | 地图样式 URL 或内联样式对象 |
StyleImageInterface 属性
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| width | number | 是 | 图像宽度(像素) |
| height | number | 是 | 图像高度(像素) |
| data | Uint8Array | 是 | 像素数据,RGBA 格式 |
| onAdd | function | 是 | 图层添加时调用,初始化 Canvas |
| render | function | 是 | 每帧调用,返回 true 表示图像已更新 |
🎨 效果说明
运行代码后,地图上会在坐标[0, 0]处显示一个脉冲动画图标:
- 内圈: 固定大小的红色圆点,带白色描边
- 外圈: 脉冲扩散效果,从中心向外逐渐扩大并淡出
- 动画周期: 1 秒完成一次脉冲循环
- 交互功能: 支持鼠标拖拽、滚轮缩放等标准交互
动画原理:
render()方法每帧被调用- 使用
performance.now()计算动画进度 - 动态计算外圈半径和透明度
- 通过
map.triggerRepaint()触发地图重绘
💡 常 见 问 题
Q1: StyleImageInterface 是什么?
A:这是一个接口,允许开发者创建动态生成的图像。通过实现onAdd()和render()方法,可以在运行时生成动画图标。
Q2: 为什么需要返回 true?
A:render()方法返回true告诉地图图像已更新,需要重新渲染。返回false则跳过重绘。
Q3: 性能影响如何?
A:每帧都会调用render()和triggerRepaint(),对于复杂动画可能影响性能。建议优化渲染逻辑或降低动画帧率。
Q4: 可以创建多个动画图标吗?
A:可以。为每个动画图标定义不同的 ID,或者使用相同的图像对象创建多个图层。
📝 练习任务
- 基础练习:修改动画周期和颜色,创建不同的脉冲效果
- 进阶挑战:实现多个不同位置的脉冲图标,每个有不同的动画周期
- 拓展思考:如何实现图标沿路径移动的动画?
🌟 最佳实践
- 性能优化: 避免在
render()中进行复杂计算,考虑预计算或缓存 - 内存管理: 对于临时图像,使用后及时清理
- 像素比例: 使用
pixelRatio参数适配高分辨率屏幕 - 动画控制: 提供启动/停止动画的机制
- 测试验证: 在不同设备上测试动画性能
- 降级方案: 为不支持 Canvas 的环境提供备用方案
🔗 延伸阅读
Map API文档
MapLibre GL JS 官方文档
[下一课预告]:将继续学习地图图层的基础知识
本文是MapLibre GL JS实践课程系列的一部分,欢迎关注收藏