animation
本文回答:setAnimation 弹跳/坠落、CSS 呼吸/光圈/扩散/荧光/旋转 —— 让 Marker 活起来。
一、setAnimation(只对默认水滴有效)
const m = new vjmap.Marker({ color: "#f00" }).setLngLat([lng, lat]);
m.addTo(map); // 注意:addTo 运行时返回 undefined,不能把链尾赋给 m
m.setAnimation("MAP_ANIMATION_BOUNCE"); // 弹跳
m.setAnimation("MAP_ANIMATION_DROP"); // 坠落
m.setAnimation("MAP_ANIMATION_NONE"); // 关闭
值:
MAP_ANIMATION_NONE:无动画MAP_ANIMATION_BOUNCE:弹跳(原地上下)MAP_ANIMATION_DROP:坠落(从上方掉落一次)
二、自定义 element 如何让 setAnimation 生效
setAnimation 通过改 marker.getElement().children[0] 的 CSS 来动画;自定义 element 必须外层再套一层 div:
const inner = document.createElement("div");
inner.className = "my-marker";
// inner 自己的样式...
const wrapper = document.createElement("div");
wrapper.appendChild(inner); // 关键:包一层
const m = new vjmap.Marker({ element: wrapper, anchor: "bottom" })
.setLngLat([lng, lat]);
m.addTo(map);
m.setAnimation("MAP_ANIMATION_BOUNCE");
三、CSS 呼吸光圈
.breath {
width: 20px; height: 20px; border-radius: 50%; background: #f00;
animation: breath 1.5s ease-in-out infinite;
}
@keyframes breath {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.4); opacity: 0.5; }
}
const el = document.createElement("div");
el.className = "breath";
new vjmap.Marker({ element: el }).setLngLat([lng, lat]).addTo(map);
四、CSS 涟漪扩散
.ripple {
position: relative; width: 20px; height: 20px;
}
.ripple::before, .ripple::after {
content: ""; position: absolute; inset: 0;
border: 2px solid #0af; border-radius: 50%;
animation: ripple 1.5s ease-out infinite;
}
.ripple::after { animation-delay: 0.75s; }
@keyframes ripple {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(4); opacity: 0; }
}
五、旋转
.spin {
width: 30px; height: 30px;
background: url("/icons/compass.png") center/cover;
animation: spin 4s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
或用 JS:
let deg = 0;
function frame() {
deg = (deg + 2) % 360;
marker.setRotation(deg);
requestAnimationFrame(frame);
}
frame();
六、vjmap 内置特效类
详见 builtin-icons。简要:
| 类 | 效果 |
|---|---|
vjmap.BreathingApertureMarker |
呼吸光圈 |
vjmap.HaloRingMarker |
光环 |
vjmap.DiffusedApertureMarker |
扩散光圈 |
vjmap.FluorescenceMarker |
荧光 |
vjmap.RotatingApertureMarker |
旋转光圈 |
vjmap.RotatingTextBorderMarker |
旋转文字边框 |
vjmap.AnimateMarkerLayer |
批量动画 marker 图层 |
使用(构造签名是 (features | {lngLat, text?}, options?: AnimateMarkerLayerOption)):
const eff = new vjmap.BreathingApertureMarker(
{ lngLat: map.toLngLat([x, y]) },
{ /* AnimateMarkerLayerOption */ }
);
eff.setMarkersWidth(30);
eff.setMarkersColors(["#0af", "#fff"]);
eff.setDotSize?.(8);
const m = eff.createMarker(); // → vjmap.Marker
m.addTo(map);
七、轨迹动画(Marker 沿路径移动)
const path = [[lng1, lat1], [lng2, lat2], [lng3, lat3]];
const marker = new vjmap.Marker().setLngLat(path[0]);
marker.addTo(map);
let i = 0, t = 0, duration = 1000;
const startTime = performance.now();
function anim() {
const elapsed = performance.now() - startTime;
const segT = (elapsed % duration) / duration;
const segI = Math.floor(elapsed / duration) % (path.length - 1);
const a = path[segI], b = path[segI + 1];
marker.setLngLat([
a[0] + (b[0] - a[0]) * segT,
a[1] + (b[1] - a[1]) * segT
]);
requestAnimationFrame(anim);
}
anim();
八、GPU 加速提示
移动 Marker 时:
.my-marker {
will-change: transform;
}
九、性能建议
- 动画 Marker 不要超过 50 个(DOM 动画很贵)。
- 大量动态图标(热力/轨迹)请改用 Symbol Layer + 数据驱动样式。
requestAnimationFrame回调中不要做复杂计算;只 setLngLat。
十、常见坑
setAnimation对自定义 element 无效 → 外层没包一层 div。- CSS 动画卡顿 → 使用
transform/opacity;避免动画width、top。 - 特效 Marker 叠太多 → 每个都是一个 div + CSS 动画,几十个就卡。
animation和will-change滥用:will-change只加在动画元素上,不要全局加。