Skip to content

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.gif306×220脉冲告警图标
tf.gif504×400雷达图标

gifUrl 支持:

  • 工程静态路径/static/images/bgif/bgif0.gif(示例采用,零网络依赖);
  • 网络地址https://your-host/marker.gif
  • 内嵌 dataURLdata: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;      // 最大可视距离(米),不传则不限制
}
参数类型默认值描述
positionCartesian3点位世界坐标,缺少时构造抛错
gifUrlstringGIF 地址,缺少时构造抛错;支持网络地址或 data:image/gif;base64,
scalenumber1图标缩放倍数
scaleByDistanceNearFarScalar近远缩放(nearDistance/farDistance 对应 nearValue/farValue),不传恒定缩放
heightReferenceHeightReferenceRELATIVE_TO_GROUND贴地方式,对齐源案例 HeightReference.RELATIVE_TO_GROUND
verticalOriginVerticalOriginBOTTOM垂直锚点,BOTTOM 让图标底部贴地
loopbooleantruefalse 时仅解码首帧并保持静态,不再推进帧索引
maxDistancenumber超过该距离(米)隐藏图标,等价 DistanceDisplayCondition(0, maxDistance)

生命周期

ts
on(viewer: Viewer): void      // 启用效果:异步 fetch + 解码 GIF 帧,完成后挂载 billboard 实体并启动帧推进(幂等)
off(viewer: Viewer): void     // 停用效果:中止加载、停止帧推进、移除实体并释放解码资源(幂等)
destroy(viewer: Viewer): void // 销毁效果,委托 off 释放全部自有资源(可重复调用)
方法参数说明
onviewer重复调用为 no-op,不会重复创建实体
offviewer未启用时调用为 no-op
destroyviewer委托 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 开销。