唯杰地图 logo唯杰地图
VJMAP
EN

animation

vjmap/references/marker/animation.md 4.8 KB176 行
原文

本文回答: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。

十、常见坑

  1. setAnimation 对自定义 element 无效 → 外层没包一层 div。
  2. CSS 动画卡顿 → 使用 transform / opacity;避免动画 width、top。
  3. 特效 Marker 叠太多 → 每个都是一个 div + CSS 动画,几十个就卡。
  4. animation 和 will-change 滥用:will-change 只加在动画元素上,不要全局加。