Appearance
BillboardGifEffect GIF 图标点效果
在指定位置挂载一个持续播放 GIF 动画的 billboard 图标,对齐源案例 PointObject/BillboardGif。 实现采用 自研 GIF 解码 + 帧推进(
gifDecoder移植自源仓库 libgif.js / omggif 核心, 纯 TypeScript,无第三方运行时依赖):JS 解码出 RGBA 帧序列,内部 timer 按 GIF 帧延迟 推进帧索引,CallbackProperty(isConstant=false)返回当前帧 PNG dataURL 给billboard.image。 为什么必须自解码:Chrome 的canvas.drawImage()对动画 GIF 只取默认(第一)帧(WHATWG 规范 + Chromium 388253004),「<img>+ canvas 逐帧抓取」拿不到后续动画帧,图标会静止。 源案例同样走此路径(SuperGif 手动解码),本实现不依赖浏览器对 GIF 的动画播放。
能力定位
BillboardGifEffect 适合:
- 动态告警标注:作战/监控点位的脉冲、闪烁、雷达类 GIF 图标;
- 重要目标持续标注:长时间运行的动画图标,无需宿主维护帧状态;
- 资源复用:示例直接消费源仓库 cesium-example 的 GIF 资源,也可传入任意网络地址或 dataURL。
与 动画点位 的 DOM overlay 不同,本效果直接驱动 billboard 实体, 随场景走投影/遮挡/贴地逻辑,并可配合 scaleByDistance / maxDistance 做距离联动。
资源来源
示例 GIF 直接使用源仓库 cesium-example(PointObject/BillboardGif)自带资源,拷贝至 packages/gis-cesium/public/static/images/bgif/:
| 文件 | 尺寸 | 用途 |
|---|---|---|
bgif0.gif | 306×220 | 脉冲告警图标 |
tf.gif | 504×400 | 雷达图标 |
gifUrl 支持:
- 工程静态路径:
/static/images/bgif/bgif0.gif(示例采用,零网络依赖); - 网络地址:
https://your-host/marker.gif; - 内嵌 dataURL:
data:image/gif;base64,...(无外部资源依赖)。
构造函数
ts
constructor(options: BillboardGifEffectOptions)ts
interface BillboardGifEffectOptions {
position: Cartesian3; // 点位位置(世界坐标),必填
gifUrl: string; // GIF 图片 URL(网络地址或 dataURL),必填
scale?: number; // 图标缩放,默认 1
scaleByDistance?: NearFarScalar; // 距离缩放,不传则恒定缩放
heightReference?: HeightReference; // 贴地方式,默认 RELATIVE_TO_GROUND
verticalOrigin?: VerticalOrigin; // 垂直锚点,默认 BOTTOM(图标底部贴地)
loop?: boolean; // 是否持续刷新动画帧,默认 true;false 仅显示首帧
maxDistance?: number; // 最大可视距离(米),不传则不限制
}| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
position | Cartesian3 | — | 点位世界坐标,缺少时构造抛错 |
gifUrl | string | — | GIF 地址,缺少时构造抛错;支持网络地址或 data:image/gif;base64, |
scale | number | 1 | 图标缩放倍数 |
scaleByDistance | NearFarScalar | — | 近远缩放(nearDistance/farDistance 对应 nearValue/farValue),不传恒定缩放 |
heightReference | HeightReference | RELATIVE_TO_GROUND | 贴地方式,对齐源案例 HeightReference.RELATIVE_TO_GROUND |
verticalOrigin | VerticalOrigin | BOTTOM | 垂直锚点,BOTTOM 让图标底部贴地 |
loop | boolean | true | false 时仅解码首帧并保持静态,不再推进帧索引 |
maxDistance | number | — | 超过该距离(米)隐藏图标,等价 DistanceDisplayCondition(0, maxDistance) |
生命周期
ts
on(viewer: Viewer): void // 启用效果:异步 fetch + 解码 GIF 帧,完成后挂载 billboard 实体并启动帧推进(幂等)
off(viewer: Viewer): void // 停用效果:中止加载、停止帧推进、移除实体并释放解码资源(幂等)
destroy(viewer: Viewer): void // 销毁效果,委托 off 释放全部自有资源(可重复调用)| 方法 | 参数 | 说明 |
|---|---|---|
on | viewer | 重复调用为 no-op,不会重复创建实体 |
off | viewer | 未启用时调用为 no-op |
destroy | viewer | 委托 off,重复调用安全 |
基础示例
ts
import { BillboardGifEffect } from '@nexa/gis-cesium'
import { Cartesian3, HeightReference, NearFarScalar } from 'cesium'
const effect = new BillboardGifEffect({
position: Cartesian3.fromDegrees(106.4537, 29.5065, 68),
gifUrl: '/static/images/bgif/bgif0.gif', // 源仓库资产
scale: 0.6,
scaleByDistance: new NearFarScalar(500, 1.0, 2000, 0.1),
heightReference: HeightReference.RELATIVE_TO_GROUND,
})
effect.on(viewer)
// 页面或场景退出时由创建方负责清理
effect.destroy(viewer)进阶示例
三点位、两种动图(bgif0.gif / tf.gif),对齐源案例 ECEF 坐标与距离缩放:
ts
import { BillboardGifEffect } from '@nexa/gis-cesium'
import { Cartesian3, HeightReference, NearFarScalar } from 'cesium'
const points = [
{ lon: 106.4537, lat: 29.5065, h: 68, gif: '/static/images/bgif/bgif0.gif' },
{ lon: 106.4555, lat: 29.5076, h: 68, gif: '/static/images/bgif/tf.gif' },
{ lon: 106.4573, lat: 29.5062, h: 27, gif: '/static/images/bgif/bgif0.gif' },
]
const effects = points.map(({ lon, lat, h, gif }) => {
const fx = new BillboardGifEffect({
position: Cartesian3.fromDegrees(lon, lat, h),
gifUrl: gif,
scale: 0.6,
scaleByDistance: new NearFarScalar(500, 1.0, 2000, 0.1),
heightReference: HeightReference.RELATIVE_TO_GROUND,
})
fx.on(viewer)
return fx
})
// 场景退出时统一清理
effects.forEach(fx => fx.destroy(viewer))实现与性能说明
- 自研解码,零第三方运行时依赖:
gifDecoder纯 TypeScript 移植自源仓库 libgif.js (SuperGif,MIT)内嵌的 omggif 核心(MIT),只做字节流解析、LZW 解压、帧合成 (disposal 0/1/2/3、透明索引、隔行扫描),输出合成后的 RGBA 帧序列,可在任意 JS 环境确定性测试(单测覆盖真实资产 bgif0.gif / tf.gif); - 帧驱动:
on(viewer)异步fetch+ 一次性解码全部帧,内部setTimeout链按每帧delayMs(GIF 未声明按 100ms)推进帧索引,CallbackProperty(isConstant=false)返回 当前帧 PNG dataURL,Cesium 每帧重新求值即得动画 —— 与浏览器 GIF 播放状态无关, headless 环境同样成立; - 帧缓存:每帧首次显示时
putImageData+toDataURL生成一次并缓存,之后直接复用, 不逐帧重复编码; - 首帧占位:GIF 未加载/解码完成前返回 1×1 透明 PNG dataURL,避免空 image 报错; 加载期间
off()/destroy()通过AbortController中止请求,不残留实体; loop: false:解码后仅显示首帧,不启动帧推进 timer;- 性能:一次性解码全部帧(如 tf.gif 504×400/43 帧)内存与耗时按帧数与尺寸线性增长, 适合少量标注点(源案例同样行为);每帧仅一次 toDataURL;
- 资源所有权:效果只创建并释放自己的
canvas、解码帧缓存与 billboard 实体,off()会停止 timer、中止未完成请求并释放引用,避免资源残留。
Cesium 版本限制
- 依赖
CallbackProperty/DistanceDisplayCondition/NearFarScalar,为 Cesium 长期稳定 API; - 已验证目标版本:Cesium 1.133.1。
清理责任
on()创建的实体与帧推进 timer 统一由destroy(viewer)(或off(viewer))释放;- 不调用
destroy会导致实体与帧推进 timer 残留(持续刷新 billboard 造成 CPU 开销),务必在页面或场景退出时清理; destroy可重复调用,幂等安全。
与原生 Cesium API 的组合方式
- 可与
viewer.entities.add()的 label/point 实体叠加,作为 GIF 动画告警图标的配套文字标注; - 需要不同动图时创建多个
BillboardGifEffect实例即可,无共享状态; - 静态场景可设
loop: false仅显示 GIF 首帧,此时不再产生逐帧 CPU 开销。