此次项目改用了React,结合Vite构建项目,要集成Cesium实现一个三维的地球。本文将从环境搭建到项目实战,一步步教你如何实现这一功能。

本文代码已上传至Gitee仓库,可直接克隆使用

git clone https://gitee.com/z-beast/cesium-react-demo.git

一、基础环境准备

cesium官网:https://cesium.com/learn/cesiumjs-learn/

Cesium 部分功能需要 AccessToken 密钥支持,申请步骤如下:

1. 访问官网注册并登录  https://ion.cesium.com/signup

2. 登录成功后,在 AccessTokens 页面下,可复制token的值

二、项目集成Cesium

1. 创建 React+TypeScript+Vite 项目

在你想放置项目的文件夹下 打开终端,执行以下命令创建基础项目(项目名称可自定义)

# 使用 npm(推荐)
npm create vite@latest cesium-react-demo -- --template react-ts

进入项目目录

cd cesium-react-demo

# 安装依赖
npm install

启动开发服务器

npm run dev

访问 http://localhost:5173 即可看到项目运行。

2. 安装依赖

npm install cesium@^1.140.0
npm install vite-plugin-cesium@^1.2.23 -D

vite-plugin-cesium:这个插件会自动处理Cesium静态资源的路径问题

3. Vite核心配置

修改项目根目录下的 vite.config.ts 文件,配置 Cesium 插件,解决静态资源加载问题:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cesium from 'vite-plugin-cesium'

export default defineConfig({
  plugins: [
    react(), 
    cesium() // 集成 Cesium 插件,自动处理资源拷贝、按需加载
  ],
  // 可选:配置路径别名(如需简化 Cesium 导入路径)
  resolve: {
    alias: {
      '@': '/src',
      'cesium-src': 'cesium/Build/Cesium' // 简化 Cesium 核心模块导入
    }
  }
})

配置说明:

  1. 插件顺序:cesium() 插件需在 react() 之后注册,确保组件能正常识别 Cesium 资源
  2. 静态资源处理:vite-plugin-cesium 会自动拷贝 Cesium 的 Build/Cesium 目录下的静态资源(如纹理、字体、Worker 脚本)到构建目录,无需手动复制
  3. 别名配置:通过 cesium-src 别名可简化导入路径(如 import 'cesium-src/Widgets/widgets.css'),避免写冗长的相对路径
  4. 打包兼容:插件已内置 Cesium 打包优化,解决 require 语法兼容、Worker 脚本打包等问题,无需额外配置 build.rollupOptions

4. 配置Token环境变量

在项目根目录创建 .env.development 文件,配置 Cesium Ion Token

VITE_CESIUM_ION_TOKEN="你的 Cesium Ion Token"
  • 前缀 VITE_ 是 Vite 环境变量的固定前缀,确保在组件中通过 import.meta.env 访问
  • 替换 你的 Cesium Ion Token 为第一步申请的真实 Token,若未配置 Token,部分在线资源将无法加载

5. Cesium 组件核心封装

创建 src/components/Cesium.tsx组件,封装 Cesium 初始化、容器挂载、生命周期管理等核心逻辑,实现组件化复用:

import { useEffect, useRef, useState } from 'react';
import * as Cesium from 'cesium';
import { Viewer, Cartesian3, Color, Ion, Entity } from 'cesium';
// 导入 Cesium 样式文件(必须引入,否则控件样式异常)
import 'cesium/Build/Cesium/Widgets/widgets.css';
import './index.scss';

const {VITE_CESIUM_ION_TOKEN} = import.meta.env


// 使用你的Token令牌
Cesium.Ion.defaultAccessToken = VITE_CESIUM_ION_TOKEN

const CesiumEarth: React.FC<{}> = () => {
  const center = [116.41, 39.52, 16000000] as [number, number, number];   
  const containerRef = useRef<HTMLDivElement>(null);
  const viewerRef = useRef<Cesium.Viewer | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);


  useEffect(() => {
    if (!containerRef.current) return;

    try {
      setIsLoading(true);
      
      // 创建 Viewer
      const viewer = new Cesium.Viewer(containerRef.current, {
        // 控件配置 显示/隐藏
        shouldAnimate: false,
        animation: false,  //动画
        baseLayerPicker: true,  //图层选择器
        fullscreenButton: true,  //全屏按钮
        vrButton: false,
        homeButton: true,  //返回默认视角
        infoBox: true,
        sceneModePicker: true,  //场景模式切换(2D/3D)
        selectionIndicator: false,
        timeline: false,  //时间轴
        navigationHelpButton: true,  //帮助说明按钮
        navigationInstructionsInitiallyVisible: false,  //帮助说明是否默认打开
        // 性能优化
        targetFrameRate: 60,
        useBrowserRecommendedResolution: false,
      });

      viewerRef.current = viewer;

      // 视角定位到中国-北京区域
      if (viewerRef.current) {
        viewerRef.current.camera.flyTo({
          destination: Cesium.Cartesian3.fromDegrees(center[0], center[1], center[2]),
          duration: 2 // 动画时长(秒),默认3
        });
      }
      
      setIsLoading(false);
    } catch (err) {
      console.error('Failed to initialize Cesium:', err);
      setError(err instanceof Error ? err.message : 'Failed to initialize Cesium');
      setIsLoading(false);
    }

    // 清理函数
    return () => {
      if (viewerRef.current) {
        viewerRef.current.destroy();
        viewerRef.current = null;
      }
    };
  }, []); // 只在组件挂载时初始化一次


  return (
    <div className="cesium-earth">
      <div ref={containerRef} className="cesium-viewer-container" />

      {isLoading && (
        <div className="loading-overlay">
          <div className="loading-spinner" />
          <p>加载地球中...</p>
        </div>
      )}
      {error && (
        <div className="error-overlay">
          <p>错误: {error}</p>
          <button onClick={() => window.location.reload()}>重试</button>
        </div>
      )}
    </div>
  )
};

export default CesiumEarth;

同目录下样式文件 index.scss

.cesium-earth {
  width: 100%;
  height: 100vh;
  position: absolute;
  overflow: hidden;

  .error-overlay {
    display: none;
  }
}

.cesium-viewer-container {
  width: 100%;
  height: 100%;

  /** 使用css隐掉控件工具*/
  .cesium-viewer-geocoderContainer,
  .cesium-widget-credits {
    display: none !important;
  }
}

.loading-overlay,
.error-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  z-index: 1000;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 15px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.error-overlay button {
  margin-top: 15px;
  padding: 8px 20px;
  background: #ff4444;
  border: none;
  color: white;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.error-overlay button:hover {
  background: #ff6666;
}

/* 响应式调整 */
@media (max-width: 768px) {
  .loading-overlay p,
  .error-overlay p {
    font-size: 14px;
    padding: 0 20px;
    text-align: center;
  }
}

封装亮点与避坑解析

  1. 生命周期管理:在 useEffect() 中初始化 Viewer,并在 return 中销毁实例,避免内存泄漏(Cesium 实例若不手动销毁,会残留大量 DOM 和事件监听)
  2. 样式引入:必须导入 cesium/Build/Cesium/Widgets/widgets.css样式文件,否则 Cesium 控件会样式错乱
  3. Token 动态配置:从环境变量读取 Token,避免硬编码,提高项目安全性和可配置性
  4. 控件自定义:通过 Viewer 配置项灵活控制控件显示,满足不同项目需求
  5. 视角初始化:通过 camera.flyTo 定位到目标区域,提升用户体验

6. 项目组件使用

在根组件中 App.tsx 中使用 Cesium.tsx

import './App.css'
import Cesium from './components/Cesium'

function App() {
  return (
   <Cesium />
  )
}

export default App

随后即可看到全屏的 Cesium 地球场景,支持鼠标拖拽旋转、滚轮缩放、控件操作等功能。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐