目录
✨前言
一、功能背景
1.1 核心功能概览
1.2 技术栈与工具
二、车辆动画
2.1 模型坐标
2.2 组合渲染
2.3 显隐状态
2.4 模型文件
三、雷达动画
3.1 创建元素
3.2 动画解析
3.3 坐标联动
3.4 交互事件
四、完整代码
4.1 属性参数
4.2 逻辑代码
加载车辆动画
控制车辆动画
加载雷达动画
控制雷达动画
4.3 模拟展示
五、注意事项
5.1 资源加载与路径问题
5.2 性能与内存管理
5.3 兼容性与调试
六、本文总结
七、更多操作
✨前言
在《Cesium 7,在 Cesium 上实现高速道路渲染》中,我们完成了高速道路的三维建模与场景搭建。本文作为续篇,将聚焦于动态元素与交互逻辑的开发,在已构建的道路场景中叠加雷达动画与车辆模型,实现 “静态场景 + 动态实体” 的完整可视化方案。通过本文分享,你将学会如何在 Cesium 道路场景中集成动态交互元素,并掌握坐标转换、动画控制与状态管理的核心技术。
一、功能背景
功能实现效果与技术架构
1.1 核心功能概览
- 车辆模型系统:加载 GLB 格式 3D 车辆模型,附带标签文本与动态扩散波纹,模拟救援车辆的实时位置。
- 雷达动画系统:基于 HTML+CSS 实现圆形波纹扩散效果,绑定经纬度坐标,支持点击交互触发事件弹窗。
- 交互控制:通过统一接口切换元素显隐状态,结合 Cesium 坐标转换实现动画元素与地图视图的动态联动。
1.2 技术栈与工具
- 框架:CesiumJS(三维渲染引擎)、JavaScript(逻辑控制)。
- 资源格式:GLB(3D 模型)、CSS3(动画样式)。
- 关键 API:
Cesium.Cartesian3.fromDegrees
:经纬度转笛卡尔坐标。viewer.entities.add
:添加模型、标签、波纹等实体。CallbackProperty
:动态更新椭圆波纹的半径与透明度。
二、车辆动画
车辆模型动画开发详解
2.1 模型坐标
模型加载与坐标计算
代码实现
loadVehicleModelAnime() {
// 初始经纬度(车辆位置)
const initLng = this.RealCarLonLat.lon;
const initLat = this.RealCarLonLat.lat;
// 移动距离与地球半径(用于坐标偏移计算)
const moveDistance = 20; // 20米
const earthRadius = 6371000; // 地球半径(米)
// 计算偏移量(简化版航向角为0°,仅水平移动)
const deltaLat = (moveDistance / earthRadius) * Math.cos(0);
const deltaLng = (moveDistance / (earthRadius * Math.cos(Cesium.Math.toRadians(initLat)))) * Math.sin(0);
const newLat = initLat + Cesium.Math.toDegrees(deltaLat);
const newLng = initLng + Cesium.Math.toDegrees(deltaLng);
// 目标位置(笛卡尔坐标)
const targetPosition = Cesium.Cartesian3.fromDegrees(newLng, newLat, 0);
// ... 后续模型实体创建代码
}
原理说明
- 坐标偏移逻辑:通过球面几何公式计算车辆在平面地图上的偏移量,
deltaLat
和deltaLng
分别对应纬度和经度方向的位移弧度,最终转换为度数生成目标坐标。 - 航向角扩展:若需车辆朝向动态变化,可将
heading
参数与车辆实时航向数据绑定(如vehicleHeading
),通过HeadingPitchRollQuaternion
动态更新模型朝向。
2.2 组合渲染
多实体组合渲染
组件拆解
实体类型 | 技术实现 | 关键属性 / 配置 |
---|---|---|
车辆模型 | viewer.entities.add + model 属性 |
uri: "/glb/8jc.glb" ,scale: 1 |
标签文本 | label 实体 |
红色填充 + 白色描边,pixelOffset: [0, -30] |
扩散波纹 | 3 层 ellipse 实体,通过 CallbackProperty 动态更新半径与透明度 |
semiMajorAxis /semiMinorAxis 随时间线性增长 |
波纹动画核心代码
for (let i = 0; i < waveCount; i++) {
const startTime = Date.now() + i * waveInterval; // 每层波纹延迟1秒启动
const waveEntity = viewer.entities.add({
position: targetPosition,
ellipse: {
semiMajorAxis: new Cesium.CallbackProperty(() => {
const elapsed = (Date.now() - startTime) % (waveCount * waveInterval);
return 10 + 2000 * (elapsed / (waveCount * waveInterval)); // 从10米扩展到2010米
}, false),
material: new Cesium.ColorMaterialProperty(
new Cesium.CallbackProperty(() => {
const alpha = 1.0 - (Date.now() - startTime) / (waveCount * waveInterval);
return Cesium.Color.RED.withAlpha(Math.max(0, alpha)); // 透明度线性衰减
}, false)
)
}
});
}
2.3 显隐状态
显隐状态统一管理
toggleVehicleShowStatus() {
if (!this.vehicleEntities || this.vehicleEntities.length === 0) return;
this.vehicleVisible = !this.vehicleVisible;
// 批量更新所有实体状态
this.vehicleEntities.forEach(entity => {
entity.show = this.vehicleVisible;
});
}
- 设计模式:采用「实体数组管理」模式,将模型、标签、波纹实体存入
vehicleEntities
数组,通过统一接口批量操作显隐状态,避免重复代码。 - 性能优化:隐藏时自动停止
CallbackProperty
的动态计算,减少无用渲染消耗。
2.4 模型文件
场景中加载的3D车辆模型文件,glb格式文件,见文章顶部。
三、雷达动画
雷达动画开发与交互逻辑
雷达动画通过 HTML DOM 元素与 CSS3 关键帧动画实现,具备轻量化、高性能的特点,可动态锚定地图坐标并响应用户交互。以下是基于代码的详细解析:
3.1 创建元素
基于 JavaScript 创建雷达容器与动画元素
实现方式:通过 document.createElement
动态创建 HTML 元素,避免直接操作静态 DOM,提升代码动态性与可维护性。
DOM 结构生成逻辑
// 创建雷达容器(圆形定位容器)
const container = document.createElement("div");
container.className = "radar_container"; // 关联CSS类
container.style.width = "200px";
container.style.height = "200px";
container.style.borderRadius = "50%"; // 圆形外观
container.style.position = "absolute"; // 绝对定位,基于地图视口坐标
container.style.zIndex = "999"; // 层级置顶,避免被地图覆盖
信息提示文本
const tip = document.createElement("div");
tip.innerText = `求助点位置:${this.realEventList.begMileageNo} - ${this.realEventList.endMileageNo}`;
tip.style.position = "absolute";
tip.style.bottom = "70%"; // 位于容器顶部
tip.style.left = "50%";
tip.style.transform = "translateX(-50%)"; // 水平居中
tip.style.color = "red";
tip.style.pointerEvents = "none"; // 允许鼠标事件穿透到地图
实现效果,提示文本固定在雷达容器正上方,显示事件位置信息,支持动态数据绑定(如 realEventList
)。
雷达波纹元素
for (let i = 0; i < 3; i++) {
const wave = document.createElement("div");
wave.className = "radar_wave"; // 关联CSS类
// 基础样式:圆形边框+阴影
wave.style.width = "20px";
wave.style.height = "20px";
wave.style.border = "1px solid red";
wave.style.borderRadius = "50%";
wave.style.boxShadow = "inset 0 0 5px 1px red"; // 内阴影增强立体感
// 定位与动画设置
wave.style.top = "50%";
wave.style.left = "50%";
wave.style.transformOrigin = "center center"; // 动画中心点
wave.style.animation = `radar_wave-animation 3s infinite`; // 引用关键帧动画
wave.style.animationDelay = `${i}s`; // 延迟启动(0s/1s/2s),形成层次感
}
3.2 动画解析
CSS 关键帧动画解析
动画定义方式:通过 JavaScript 动态向 head
标签注入 <style>
标签,避免全局样式污染。
if (!document.getElementById("radar-wave-style")) {
const style = document.createElement("style");
style.id = "radar-wave-style";
style.innerHTML = `
@keyframes radar_wave-animation {
0% {
transform: translate(-50%, -50%) scale(0); /* 初始状态:缩放为0,位于容器中心 */
opacity: 1; /* 完全不透明 */
}
100% {
transform: translate(-50%, -50%) scale(6); /* 最终状态:缩放6倍,覆盖容器范围 */
opacity: 0; /* 完全透明 */
}
}
`;
document.head.appendChild(style);
}
核心参数说明:
属性 | 作用描述 |
---|---|
transform: scale() |
控制波纹半径扩展,scale(6) 表示从初始尺寸(20px)扩展至 120px(覆盖 200px 容器)。 |
opacity |
实现波纹渐隐效果,避免动画结束后残留视觉痕迹。 |
animationDelay |
每层波纹延迟启动,形成 “多层扩散” 的动态层次感(如第 1 层 0s 启动,第 2 层 1s 启动)。 |
3.3 坐标联动
地图坐标与屏幕坐标联动
toggleRadarShowStatus() {
if (this.radarVisible) {
// 初始化坐标转换
this.radarPosition = Cesium.Cartesian3.fromDegrees(this.radarLonLat.lon, this.radarLonLat.lat);
const canvasPos = viewer.scene.cartesianToCanvasCoordinates(this.radarPosition);
// 定位DOM元素
this.radarOverlayDom.style.left = `${canvasPos.x - 100}px`; // 水平居中(容器宽200px)
this.radarOverlayDom.style.top = `${canvasPos.y - 100}px`; // 垂直居中
// 每帧更新位置(监听场景预渲染事件)
this.updateRadarPositionHandler = viewer.scene.preRender.addEventListener(() => {
const pos = viewer.scene.cartesianToCanvasCoordinates(this.radarPosition);
this.radarOverlayDom.style.left = `${pos.x - 100}px`;
this.radarOverlayDom.style.top = `${pos.y - 100}px`;
});
} else {
// 隐藏时移除监听
if (this.updateRadarPositionHandler) {
viewer.scene.preRender.removeEventListener(this.updateRadarPositionHandler);
this.updateRadarPositionHandler = null;
}
}
}
核心逻辑,通过 cartesianToCanvasCoordinates
将 Cesium 三维坐标转换为浏览器视口坐标,确保雷达容器始终锚定在地图指定位置,支持地图缩放、平移时的实时跟随。
3.4 交互事件
交互事件与信息展示
// 点击雷达触发事件弹窗
container.addEventListener("click", () => {
this.EventDialogVisible = true; // 显示Vue弹窗组件
});
// 提示文本动态更新(示例)
const tip = document.createElement("div");
tip.innerText = `求助点坐标:${this.realEventList.begMileageNo} - ${this.realEventList.endMileageNo}`;
交互扩展,可结合 Vue/React 等前端框架实现弹窗数据绑定,展示事件详情(如救援进度、预计到达时间)。
四、完整代码
4.1 属性参数
// 雷达位置的经纬度坐标(地理坐标系)
radarLonLat: {
lon: 117.610995,
lat: 34.399076,
},
// 雷达动画显示状态控制
radarVisible: false,
// 雷达动画的DOM容器引用(用于操作HTML元素)
radarOverlayDom: null,
// 雷达在Cesium中的三维坐标(笛卡尔坐标系)
radarPosition: null,
// 雷达位置更新事件句柄(用于绑定/移除渲染循环监听)
updateRadarPositionHandler: null,
// 实际事件数据(存储救援相关信息)
// 救援车辆的初始经纬度坐标(地理坐标系)
RealCarLonLat: {
lon: 117.617227,
lat: 34.324461,
},
4.2 逻辑代码
加载车辆动画
//⭐ 加载车辆动画,默认隐藏
loadVehicleModelAnime() {
// 获取初始经纬度坐标
const initLng = this.RealCarLonLat.lon;
const initLat = this.RealCarLonLat.lat;
// 车辆移动距离(米)
const moveDistance = 20;
// 地球半径(米)
const earthRadius = 6371000;
// 初始朝向(0表示正北方向)
const heading = 0;
// 计算经纬度变化量(基于球面几何)
let deltaLat = (moveDistance / earthRadius) * Math.cos(heading);
let deltaLng = (moveDistance / (earthRadius * Math.cos(this.Cesium.Math.toRadians(initLat)))) * Math.sin(heading);
// 转换为度数
let newLat = initLat + this.Cesium.Math.toDegrees(deltaLat);
let newLng = initLng + this.Cesium.Math.toDegrees(deltaLng);
// 创建目标位置(Cartesian3坐标)
const targetPosition = this.Cesium.Cartesian3.fromDegrees(newLng, newLat, 0);
// 初始化车辆实体数组
this.vehicleEntities = [];
// 初始状态为隐藏
this.vehicleVisible = false;
// 模型实体 - 加载3D车辆模型
const vehicleModel = this.viewer.entities.add({
name: "车辆模型",
position: targetPosition,
model: {
uri: "/glb/8jc.glb", // 模型文件路径
scale: 1, // 初始缩放比例
minimumPixelSize: 20, // 最小像素尺寸
maximumScale: 40, // 最大缩放比例
},
// 设置模型朝向(航向角、俯仰角、滚转角)
orientation: this.Cesium.Transforms.headingPitchRollQuaternion(
targetPosition,
new this.Cesium.HeadingPitchRoll(
this.Cesium.Math.toRadians(-170), // 航向角(-170度)
this.Cesium.Math.toRadians(0), // 俯仰角(0度)
this.Cesium.Math.toRadians(0) // 滚转角(0度)
)
),
show: false // 初始不显示
});
this.vehicleEntities.push(vehicleModel);
// 标签实体 - 显示车辆名称
const labelEntity = this.viewer.entities.add({
position: targetPosition,
label: {
text: "拯救车", // 标签文字
font: "14px sans-serif", // 字体样式
fillColor: this.Cesium.Color.RED, // 文字颜色
outlineColor: this.Cesium.Color.WHITE, // 文字轮廓颜色
outlineWidth: 1, // 轮廓宽度
style: this.Cesium.LabelStyle.FILL_AND_OUTLINE, // 填充加轮廓样式
verticalOrigin: this.Cesium.VerticalOrigin.BOTTOM, // 文字垂直对齐底部
pixelOffset: new this.Cesium.Cartesian2(0, -30), // 文字偏移量(向上偏移30像素)
disableDepthTestDistance: Number.POSITIVE_INFINITY // 禁用深度测试
},
show: false // 初始不显示
});
this.vehicleEntities.push(labelEntity);
// 动态扩散波纹效果 - 创建多个波纹实体
const waveCount = 3; // 波纹数量
const waveInterval = 1000; // 波纹生成间隔(毫秒)
for (let i = 0; i < waveCount; i++) {
// 计算每个波纹的开始时间
const startTime = Date.now() + i * waveInterval;
const waveEntity = this.viewer.entities.add({
position: targetPosition,
ellipse: {
// 动态椭圆长轴(随时间变化)
semiMajorAxis: new this.Cesium.CallbackProperty(() => {
let elapsed = (Date.now() - startTime) % (waveCount * waveInterval);
let scale = elapsed / (waveCount * waveInterval);
return 10 + 2000 * scale; // 从10米逐渐扩大到2010米
}, false),
// 动态椭圆短轴(随时间变化)
semiMinorAxis: new this.Cesium.CallbackProperty(() => {
let elapsed = (Date.now() - startTime) % (waveCount * waveInterval);
let scale = elapsed / (waveCount * waveInterval);
return 10 + 2000 * scale; // 从10米逐渐扩大到2010米
}, false),
// 动态颜色(透明度随时间变化)
material: new this.Cesium.ColorMaterialProperty(
new this.Cesium.CallbackProperty(() => {
let elapsed = (Date.now() - startTime) % (waveCount * waveInterval);
let alpha = 1.0 - elapsed / (waveCount * waveInterval); // 从完全不透明到完全透明
return this.Cesium.Color.RED.withAlpha(alpha); // 红色渐变
}, false)
),
height: 0, // 高度为0(贴地)
outline: false, // 无轮廓
classificationType: this.Cesium.ClassificationType.BOTH, // 贴地显示
},
show: false // 初始不显示
});
this.vehicleEntities.push(waveEntity);
}
},
控制车辆动画
// 控制车辆动画显示/隐藏状态
toggleVehicleShowStatus() {
// 如果实体数组不存在或为空,则直接返回
if (!this.vehicleEntities || this.vehicleEntities.length === 0) return;
// 切换显示状态
this.vehicleVisible = !this.vehicleVisible;
// 遍历所有车辆相关实体,统一设置显示状态
this.vehicleEntities.forEach(entity => {
entity.show = this.vehicleVisible;
});
},
这里的 loadVehicleModelAnime函数 用来创建车辆动画,默认隐藏;toggleVehicleShowStatus函数 用来控制车辆动画,首次执行,显示车辆动画,再次执行,隐藏车辆动画,循环往复。
加载雷达动画
//⭐ 创建雷达动画,默认隐藏
loadRadarLocationAnime() {
// 如果雷达DOM已存在,则直接返回(避免重复创建)
if (this.radarOverlayDom) return;
// 创建雷达容器div(圆形居中布局)
const container = document.createElement("div");
container.className = "radar_container";
// 设置样式
container.style.position = "absolute"; // 绝对定位
container.style.width = "200px"; // 宽度
container.style.height = "200px"; // 高度
container.style.borderRadius = "50%"; // 圆形
container.style.overflow = "visible"; // 允许内容溢出(显示提示文字)
container.style.pointerEvents = "auto"; // 允许鼠标事件
container.style.zIndex = "999"; // 置于顶层
container.style.display = "flex"; // 弹性布局
container.style.alignItems = "center"; // 垂直居中
container.style.justifyContent = "center"; // 水平居中
container.style.cursor = "pointer"; // 鼠标悬停显示手型
container.style.margin = "auto"; // 自动外边距
container.style.border = "2px solid transparent"; // 默认透明边框
// 创建提示文字元素
const tip = document.createElement("div");
tip.innerText = `求助点位置:${this.realEventList.begMileageNo} - ${this.realEventList.endMileageNo}`; // 显示求助点位置信息
tip.style.textShadow = "2px 3px 1px black"; // 文字阴影效果
tip.style.position = "absolute"; // 绝对定位
tip.style.bottom = "70%"; // 放在容器底部上方
tip.style.left = "50%"; // 水平居中
tip.style.transform = "translateX(-50%)"; // 水平居中修正
tip.style.padding = "2px 6px"; // 内边距
tip.style.font = "14pt"; // 字体大小
tip.style.color = "red"; // 文字颜色
tip.style.borderRadius = "4px"; // 圆角边框
tip.style.whiteSpace = "nowrap"; // 禁止换行
tip.style.pointerEvents = "none"; // 不影响鼠标事件
tip.style.userSelect = "none"; // 禁止文字选中
tip.style.opacity = "1"; // 初始完全不透明
tip.style.transition = "opacity 0.3s ease"; // 透明度过渡效果
container.appendChild(tip); // 将提示文字添加到容器中
// 添加点击事件(点击雷达显示事件详情对话框)
container.addEventListener("click", () => {
this.EventDialogVisible = true;
});
// 添加CSS关键帧动画样式(页面只添加一次)
if (!document.getElementById("radar-wave-style")) {
const style = document.createElement("style");
style.id = "radar-wave-style";
style.innerHTML = `
@keyframes radar_wave-animation {
0% {
transform: translate(-50%, -50%) scale(0); // 初始状态:缩小到0
opacity: 1; // 完全不透明
}
100% {
transform: translate(-50%, -50%) scale(6); // 最终状态:放大6倍
opacity: 0; // 完全透明
}
}
`;
document.head.appendChild(style); // 将样式添加到head中
}
// 创建3个雷达波纹div(依次延迟形成波纹效果)
for (let i = 0; i < 3; i++) {
const wave = document.createElement("div");
wave.className = "radar_wave";
// 设置波纹样式
wave.style.position = "absolute"; // 绝对定位
wave.style.width = "20px"; // 宽度
wave.style.height = "20px"; // 高度
wave.style.border = "1px solid red"; // 红色边框
wave.style.borderRadius = "50%"; // 圆形
wave.style.boxShadow = "inset 0 0 5px 1px red"; // 内阴影效果
wave.style.top = "50%"; // 垂直居中
wave.style.left = "50%"; // 水平居中
wave.style.transformOrigin = "center center"; // 变换中心点
wave.style.transform = "translate(-50%, -50%) scale(0)"; // 初始缩小到0
wave.style.animation = `radar_wave-animation 3s infinite`; // 应用动画
wave.style.animationDelay = `${i}s`; // 依次延迟(0s, 1s, 2s)
container.appendChild(wave); // 将波纹添加到容器中
}
this.radarOverlayDom = container; // 缓存雷达DOM
this.viewer.container.appendChild(container); // 将雷达添加到Cesium容器中
},
控制雷达动画
// 控制雷达动画显示/隐藏状态
toggleRadarShowStatus() {
// 如果雷达DOM不存在,则先创建
if (!this.radarOverlayDom) {
this.loadRadarLocationAnime();
}
// 切换显示状态
this.radarVisible = !this.radarVisible;
if (this.radarVisible) {
// 计算雷达对应的Cesium笛卡尔坐标(缓存)
this.radarPosition = Cesium.Cartesian3.fromDegrees(
this.radarLonLat.lon,
this.radarLonLat.lat
);
// 首次设置雷达位置
const canvasPos = this.viewer.scene.cartesianToCanvasCoordinates(this.radarPosition);
if (canvasPos) {
// 计算DOM位置(居中显示)
this.radarOverlayDom.style.left = `${canvasPos.x - 100}px`; // 宽度的一半
this.radarOverlayDom.style.top = `${canvasPos.y - 100}px`; // 高度的一半
this.radarOverlayDom.style.display = "flex"; // 显示雷达
}
// 注册渲染循环监听(每帧更新雷达位置)
this.updateRadarPositionHandler = this.viewer.scene.preRender.addEventListener(() => {
const updatedCanvasPos = this.viewer.scene.cartesianToCanvasCoordinates(this.radarPosition);
if (updatedCanvasPos) {
// 实时更新DOM位置,保持与Cesium场景同步
this.radarOverlayDom.style.left = `${updatedCanvasPos.x - 100}px`;
this.radarOverlayDom.style.top = `${updatedCanvasPos.y - 100}px`;
}
});
} else {
// 隐藏雷达并移除渲染循环监听
this.radarOverlayDom.style.display = "none";
if (this.updateRadarPositionHandler) {
this.updateRadarPositionHandler(); // 移除监听
this.updateRadarPositionHandler = null; // 清空引用
}
}
},
这里的 loadRadarLocationAnime函数 用来创建雷达动画,默认隐藏;toggleRadarShowStatus函数 用来控制雷达动画,首次执行,显示雷达动画,再次执行,隐藏雷达动画,循环往复。
4.3 模拟展示
事件组件
// 发送事件通知其他组件需要显示雷达和车辆动画
// 参数1: true 表示需要显示动画
// 参数2: this.realEventList 当前事件数据(如求助点位置等信息)
this.$bus.$emit('brotherEvent1', true, this.realEventList);
// 发送事件通知其他组件需要隐藏雷达和车辆动画
// 参数1: false 表示需要隐藏动画
// 参数2: 未传递(此时参数2为undefined)
this.$bus.$emit('brotherEvent1', false);
地图组件
// 事件推送和动画显示
// 监听名为 'brotherEvent1' 的事件(来自兄弟组件或其他模块)
this.$bus.$on('brotherEvent1', (res, data) => {
// 如果事件返回 true,表示需要显示雷达和车辆动画
if (res) {
// 立即更新事件数据(不需要延迟)
this.realEventList = data;
// 延迟 1 秒后执行以下操作(确保 DOM 已更新)
setTimeout(() => {
// 加载雷达动画(如果尚未加载)
this.loadRadarLocationAnime();
// 显示雷达动画
this.toggleRadarShowStatus();
// 加载车辆模型动画(如果尚未加载)
this.loadVehicleModelAnime();
// 显示车辆模型动画
this.toggleVehicleShowStatus();
}, 1000); // 延迟 1 秒执行
// 如果事件返回 false,表示需要隐藏雷达和车辆动画
} else if (res === false) {
// 隐藏雷达动画
this.toggleRadarShowStatus();
// 隐藏车辆模型动画
this.toggleVehicleShowStatus();
}
});
这里通过事件组件的 WebSocket 推流数据状态和 EventBus 通信控制动画显示/隐藏:当收到 brotherEvent1(true, data)
时,更新事件数据并延迟1秒加载雷达和车辆动画;收到 brotherEvent1(false)
时直接隐藏动画;根据需求自行更改。
五、注意事项
注意事项与优化建议
5.1 资源加载与路径问题
- 模型路径:确保 GLB 文件路径正确(如
uri: "/static/glb/8jc.glb"
),避免跨域问题(可配置服务器 CORS 或使用相对路径)。本文代码里的 uri: "/glb/8jc.glb", 模型文件是放在public文件下的。
5.2 性能与内存管理
- 实体清理:使用
viewer.entities.removeAll()
销毁不再需要的车辆实体,避免内存泄漏。 - 动画频率:雷达波纹
waveInterval
建议设为 500-1000ms,过短会导致 CPU 占用过高;车辆波纹waveCount
不建议超过 5 层。
5.3 兼容性与调试
- 浏览器支持:Cesium 依赖 WebGL 2.0,需确保用户浏览器支持(可添加
webgl-lint
库进行检测)。 - 坐标调试:通过
console.log(canvasPos)
打印屏幕坐标,辅助定位雷达偏移问题;使用 Cesium 调试工具viewer.scene.debugShowFramesPerSecond = true
监控帧率变化。
六、本文总结
本文总结与扩展方向
扩展方向
本文通过 Cesium 实体系统与 HTML 动画结合,实现了车辆与雷达的动态交互效果。实际项目中可进一步扩展:
- 数据驱动:接入 WebSocket 实时更新车辆位置与雷达事件数据。
- 视觉增强:为车辆模型添加灯光效果(
ModelStyle
),雷达波纹支持多颜色渐变。 - 交互升级:实现鼠标悬停时车辆信息弹窗、雷达波纹点击缩放等高级功能。
延伸阅读:
- Cesium 7 原文链接(含高速道路完整代码)
- Cesium 官方模型加载文档
- CSS3 动画性能优化指南
通过两篇文章的结合,可构建从 “基础场景→动态交互→业务逻辑” 的完整 Cesium 可视化能力,适用于智慧交通、数字孪生等复杂场景开发。
本文围绕 Cesium 中动态元素的核心需求,详细讲解了车辆模型与雷达动画的完整实现流程,涵盖坐标计算、实体渲染、交互控制及性能优化。结合前篇道路场景开发,可构建 “静态场景 + 动态交互” 的完整可视化方案,适用于智慧交通、应急救援等领域。完整项目代码可通过留言、私信作者获取,欢迎一起交流探讨 Cesium 开发技巧!
七、更多操作
请看,CesiumJS Develop 个人专栏