Cesium CSS 元素教程

CSS 元素 · CSS DOM Overlay · ▶ 在线运行案例

你将学到什么

  • 不用 Entity,用 原生 HTML 做信息面板
  • postRender 每帧把世界坐标投影到屏幕像素
  • SceneTransforms.worldToWindowCoordinates 坐标转换
  • 背面/过远时隐藏 DOM

效果说明

北京坐标处有一个可点击的蓝色边框 div「2dDOM」,随相机移动始终钉在地标上方;过远或转到地球背面时自动隐藏。

核心概念

Cesium 内置 HtmlOverlay / InfoBox 等方案;本案例演示 最小自实现

复制代码
地理坐标 (Cartesian3)
`↓ worldToWindowCoordinates
屏幕像素 (x, y)
    ↓ CSS transform: translate
DOM 定位
`

容器层级

复制代码
const css2dContainer = document.createElement('div');
`Object.assign(css2dContainer.style, {
    position: 'absolute', top: '0', left: '0',
    pointerEvents: 'none',  // 容器穿透,子元素可单独开启
    zIndex: '1',
});
box.appendChild(css2dContainer);
`

postRender 同步

复制代码
viewer.scene.postRender.addEventListener(() => {
`const windowCoord = Cesium.SceneTransforms.worldToWindowCoordinates(
        viewer.scene, position
    );
    if (windowCoord) {
        DOM.style.transform = ``translate(${windowCoord.x}px, ${windowCoord.y - offsetHeight}px)``;
    }
    // 距离过远则隐藏
    const maxDistance = ...;
    DOM.style.display = distance > maxDistance ? 'none' : 'block';
});
`

::: tip 与 Three.js CSS2DRenderer 对比 思路相同:每帧投影 + translate。Cesium 需自己算 maxDistance 处理背面。 :::

实现步骤

  • 在 Viewer 容器外再套一层 css2dContainer
  • 创建样式化 div,设置 pointerEvents: 'all' 以响应点击
  • setCss2dDom(viewer, DOM, [lon, lat, height]) 注册 postRender
  • 返回 destroy 函数:移除 DOM 并取消监听

代码要点

复制代码
  import * as Cesium from 'cesium'


  const box = document.getElementById('box')



  // 创建一个专门用于放置2dDOM的容器
  const css2dContainer = document.createElement('div')



  Object.assign(css2dContainer.style, {



  position: 'absolute',



  top: '0',



  left: '0',



  pointerEvents: 'none',



  zIndex: '1',



  })



  box.appendChild(css2dContainer)



  const viewer = new Cesium.Viewer(box, {



  animation: false,//是否创建动画小器件,左下角仪表



  baseLayerPicker: false,//是否显示图层选择器,右上角图层选择按钮



  baseLayer: false, // 不显示默认图层



  fullscreenButton: false,//是否显示全屏按钮,右下角全屏选择按钮



  timeline: false,//是否显示时间轴



  infoBox: false,//是否显示信息框



  })



  const url = GLOBAL_CONFIG.getLayerUrl()



  const layer = Cesium.ImageryLayer.fromProviderAsync(



  Cesium.ArcGisMapServerImageryProvider.fromUrl(url)



  )



  viewer.imageryLayers.add(layer)



  // 创建2dDOM
  const DOM = document.createElement('div')



  // 样式 
  Object.assign(DOM.style, {



  width: '100px',



  height: '30px',



  border: '1px solid blue',



  color: 'white',



  fontSize: '20px',



  cursor: 'pointer'



  })



  DOM.innerHTML = '2dDOM'



  setCss2dDom(viewer, DOM, [116.46, 39.92, 0]) // 设置2dDOM 移动



  viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.46, 39.92), point: { pixelSize: 10 } }) // 创建测试点



  viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(116.46, 39.92, 10000000) }) // 定位



  /设置2dDOM 移动/
  function setCss2dDom(viewer, DOM, position) {



  if (!position) return



  if (!(position instanceof Cesium.Cartesian3)) position = Cesium.Cartesian3.fromDegrees(...position)



  Object.assign(DOM.style, {



  pointerEvents: 'all',



  zIndex: 'auto',



  })



  const { offsetHeight } = DOM



  const { camera, scene } = viewer



  css2dContainer.appendChild(DOM)



  const destroy = viewer.scene.postRender.addEventListener(() => {



  const windowCoord = Cesium.SceneTransforms.worldToWindowCoordinates(viewer.scene, position)



  if (windowCoord) {



  DOM.style.transform = `translate(${windowCoord.x}px, ${windowCoord.y - offsetHeight}px)`



  }



  const maxDistance = scene.globe.ellipsoid.cartesianToCartographic(camera.position).height + scene.globe.ellipsoid.maximumRadius



  Cesium.Cartesian3.distance(camera.position, position) > maxDistance ? DOM.style.display = 'none' : DOM.style.display = 'block'



  })



  return () => {



  viewer.css2dContainer.removeChild(DOM)



  destroy()



  }


  `}
  `

完整源码:GitHub

小结