明天的明天 永远的永远 未知的一切 我与你一起承担 ??

是非成败转头空 青山依旧在 几度夕阳红 。。。
  博客园  :: 首页  :: 管理

用Three.js打造会转的地球

Posted on 2025-05-28 16:26  且行且思  阅读(163)  评论(0)    收藏  举报
Three.js 是一个用于在网页浏览器中创建和显示3D图形的JavaScript库。它基于WebGL技术,简化了复杂的3D渲染过程,使得开发者不需要深入了解WebGL的底层API就能快速上手创建丰富的3D内容。Three.js提供了大量的功能,包括但不限于几何体、材质、光源、相机、动画和加载器等,这些功能被封装成易于使用的接口,大大降低了3D图形编程的门槛。

创建画布:

首先我们要创建一个用于绘制3D图形的画布(即:3D的绘制容器),而这个画布就是<canvas>元素,并且设置其id属性来方便引用。所以在 <body>的第一行添加如下

<canvas id="webglcanvas"></canvas>

脚本部分:

我们要展现一个3D地球,但是在我们页面显示的其实相当于一个镜头拍摄一个立体图形得到的效果。既然我们要拍摄,那当然少不了一些必要的设备与环境了。

let canvas,
    camera,
    scene,
    renderer,
    group;

这里我们一一介绍实例化了哪些对象:

  • canvas:绘制图形的容器,Three.js 将在这个元素上渲染3D场景。
  • camera:Three.js 中的相机对象,定义了观察者的位置和视角,它决定了场景中哪些部分是可见的。
  • scene:Three.js 中的场景对象,包含了所有的3D模型、光源、组等。
  • renderer:Three.js 中的渲染器对象,负责将场景的3D对象转换为2D图像,并渲染到canvas元素上。
  • group:Three.js 中的组对象,组对象是一个容器,可以用来组织和管理多个3D对象,可以将多个3D对象添加到一个组中进行操作。

源码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Globe - Airwallex Style</title>
    <script src="three.min.js"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
        
        body {
            margin: 0;
            overflow: hidden;
            background-color: #ffffff;
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            color: #fff;
        }
        
        #container {
            position: relative;
            width: 100vw;
            height: 100vh;
            background: #ffffff;
            display: flex;
            align-items: center;
            overflow: hidden;
        }

        .dark-section {
            width: 100%;
            background: #1f2225;
            position: relative;
            overflow: hidden;
        }
        
        #content-wrapper {
            position: relative;
            width: 100%;
            height: auto;
            display: flex;
            align-items: center;
            padding: 80px 0 80px 8%;
            box-sizing: border-box;
            max-width: none;
            margin: 0;
            overflow: visible;
            min-height: 540px;
        }
        
        #text-content {
            width: 66.666%;
            z-index: 1;
            padding-right: 40px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            height: 100%;
        }
        
        #globe-container {
            position: absolute;
            right: -10%;
            width: 58.333%;
            height: 100%;
            overflow: visible;
            display: flex;
            align-items: center;
        }
        
        #webglcanvas {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-40%, -50%) scale(1.5);
            cursor: grab;
        }
        
        #webglcanvas:active {
            cursor: grabbing;
        }
        
        h1 {
            font-size: 52px;
            line-height: 1.2;
            margin: 0 0 24px;
            background: linear-gradient(90deg, #FFFFFF 0%, rgba(255, 255, 255, 0.6) 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            font-weight: 600;
            max-width: 800px;
        }
        
        p {
            font-size: 18px;
            line-height: 1.6;
            color: rgba(255, 255, 255, 0.7);
            margin: 0 0 40px;
            max-width: 680px;
        }
        
        .stats-container {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 80px;
            margin-top: 32px;
            max-width: none;
            position: relative;
            width: 110%;
        }
        
        .stat-item {
            text-align: left;
            position: relative;
            z-index: 2;
            min-width: 240px;
            white-space: nowrap;
        }

        .stat-item:last-child {
            position: static;
        }

        .stat-number {
            font-size: 36px;
            font-weight: 600;
            margin: 0 0 8px;
            background: linear-gradient(135deg, #FF4F42 0%, #FF8E3C 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }
        
        .stat-label {
            font-size: 16px;
            color: rgba(255, 255, 255, 0.7);
            line-height: 1.4;
            white-space: nowrap;
        }
        
        @media (max-width: 1200px) {
            #content-wrapper {
                min-height: 480px;
            }
            #globe-container {
                right: -5%;
                height: 480px;
            }
            #webglcanvas {
                transform: translate(-35%, -50%) scale(1.3);
            }
            .stats-container {
                gap: 60px;
                width: 100%;
            }
            .stat-item {
                min-width: 220px;
            }
        }


        .css-1mzq4wf {
            opacity:1;
            padding-right:24px;
            -webkit-transition:opacity 300ms ease-out;
            transition:opacity 300ms ease-out;
            color: #1976d2;
        }
        .css-1mzq4wf:hover {
            opacity:0;
        }

        @media (hover:hover) {
        .css-1mzq4wf {
            opacity:0;
         }
        }
        .css-u81etd {
         padding-right:24px;
         -webkit-transition:opacity 300ms ease-out;
         transition:opacity 300ms ease-out;
        }
    </style>
</head>
<body>


      
    <div id="container">
        <div class="dark-section">
            <div id="content-wrapper">
                <div id="text-content">
                    <h1>线上高效开户,节约成本</h1>
                    <p>即刻开启全球展业之旅。空中云汇企业账户覆盖全球多市场多币种,安全性能卓越。线上快速开户入驻,享受优质的客户服务,并且和 Xero、Amazon、Shopify 等主流平台无缝集成。</p>
                    <div class="stats-container">
                        <div class="stat-item">
                            <div class="stat-number">50+</div>
                            <div class="stat-label">个国家/地区创建本地银行账户</div>
                        </div>
                        <div class="stat-item">
                            <div class="stat-number">170+</div>
                            <div class="stat-label">个国家/地区收单</div>
                        </div>
                        <div class="stat-item">
                            <div class="stat-number">150+</div>
                            <div class="stat-label">个国家/地区国际付款</div>
                        </div>
                    </div>
                    <div class="account-link-container">
                        <a href="#" class="account-link">
                            <span class="link-text">创建全球收款账户</span>
                            <svg width="18" height="12" viewBox="0 0 18 12" class="arrow-icon" style="color: #612fff;">
                                <g fill="none" fill-rule="evenodd">
                                    <path d="M-943-3049H497v9442H-943z"></path>
                                    <path d="M-2-3h16v16H-2z"></path>
                                    <path d="M8.615 6H1a1 1 0 1 1 0-2h7.613L7.139 2.526a1 1 0 1 1 1.414-1.414l3.182 3.182a1 1 0 0 1 0 1.414L8.553 8.89A1 1 0 0 1 7.14 7.476L8.615 6z" fill="currentColor"></path>
                                </g>
                            </svg>
                        </a>
                    </div>
                </div>
                <div id="globe-container">
                    <canvas id="webglcanvas"></canvas>
                </div>
            </div>
        </div>
    </div>
    <style>
        .account-link-container {
            margin-top: 32px;
            position: relative;
            z-index: 2;
        }
        
        .account-link {
            display: inline-flex;
            align-items: center;
            text-decoration: none;
            cursor: pointer;
            position: relative;
            color: #612fff;
            margin-bottom: 24px;
            white-space: nowrap;
            height: 24px;
        }
        
        .link-text {
            opacity: 0;
            transition: opacity 300ms ease-out;
            color: #612fff;
            white-space: nowrap;
            display: inline-block;
            line-height: 24px;
        }

        .arrow-icon {
            position: absolute;
            transition: all 300ms ease-out;
            color: #612fff;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
        }
        
        .account-link:hover .arrow-icon {
            left: 135px;
        }

        @media (hover: hover) {
            .link-text {
                opacity: 0;
            }
            .account-link:hover .link-text {
                opacity: 1;
            }
        }
    </style>
    <script>
        let canvas, camera, scene, renderer, group;
        let isDragging = false;
        let autoRotate = true;
        let previousMousePosition = { x: 0, y: 0 };
        const ROTATION_SPEED = 0.002; // 固定的旋转速度常量
        
        function init() {
            // Scene setup
            scene = new THREE.Scene();
            
            // Camera setup
            camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 4000);
            camera.position.z = 1800;
            
            // Renderer setup
            canvas = document.getElementById('webglcanvas');
            renderer = new THREE.WebGLRenderer({
                canvas: canvas,
                antialias: true,
                alpha: true
            });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            
            // Group for the globe
            group = new THREE.Group();
            scene.add(group);
            
            // Create base sphere
            createGlobe();
            
            // Add event listeners
            window.addEventListener('resize', onWindowResize, false);
            canvas.addEventListener('mousedown', onMouseDown, false);
            canvas.addEventListener('mousemove', onMouseMove, false);
            canvas.addEventListener('mouseup', onMouseUp, false);
            canvas.addEventListener('mouseleave', onMouseUp, false);

             // 远程JSON数据加载
             async function loadRemoteJSON(url) {
                try {
                    
                    const response = await fetch(url);
                    const data = await response.json();
                    //console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>远程JSON数据加载>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
                    console.log(data)

                    renderPoints(data);
                } catch (error) {
                    console.error("加载远程JSON失败:", error);
                }
            }
            // 加载远程JSON文件
            loadRemoteJSON('orangeDark.json');
        }

         // 坐标点渲染函数
         function renderPoints(pointsData) {
              // 清除现有标记点
              group.children.filter(child => child.type === 'Points').forEach(p => group.remove(p));
              
              const positions = new Float32Array(pointsData.length * 3);
              pointsData.forEach((point, index) => {
                  // 将经纬度转换为球面坐标
                  const phi = (90 - point.lat) * (Math.PI / 180);
                  const theta = (point.lng + 180) * (Math.PI / 180);
                  const radius = 350; // 紧贴球体表面
                  
                  positions[index * 3] = radius * Math.sin(phi) * Math.cos(theta);
                  positions[index * 3 + 1] = radius * Math.cos(phi);
                  positions[index * 3 + 2] = radius * Math.sin(phi) * Math.sin(theta);
              });

              const geometry = new THREE.BufferGeometry();
              geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
              
              const material = new THREE.PointsMaterial({
                  color: 0x978172, // 柔和色
                  size: 10.5,
                  sizeAttenuation: true,
                  transparent: false,
                  opacity: 1
              });
              
              const points = new THREE.Points(geometry, material);
              group.add(points);
          }
        
        function createGlobe() {
            // Create the main sphere geometry
            const geometry = new THREE.SphereGeometry(350, 64, 64);
            
            // Create a custom shader material for the globe
            const material = new THREE.ShaderMaterial({
                uniforms: {
                    time: { value: 0 },
                    color1: { value: new THREE.Color('#2a2e32') },
                    color2: { value: new THREE.Color('#3a4045') }
                },
                vertexShader: `
                    varying vec2 vUv;
                    varying vec3 vNormal;
                    
                    void main() {
                        vUv = uv;
                        vNormal = normalize(normalMatrix * normal);
                        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                    }
                `,
                fragmentShader: `
                    uniform vec3 color1;
                    uniform vec3 color2;
                    uniform float time;
                    
                    varying vec2 vUv;
                    varying vec3 vNormal;
                    
                    void main() {
                        float intensity = pow(1.0 - abs(dot(vNormal, vec3(0.0, 0.0, 1.0))), 2.0);
                        vec3 color = mix(color1, color2, intensity + sin(time * 0.001 + vUv.x * 10.0) * 0.1);
                        gl_FragColor = vec4(color, 0.3);
                    }
                `,
                transparent: true
            });
            
            const globe = new THREE.Mesh(geometry, material);
            group.add(globe);
            
            // Add wireframe
            const wireframe = new THREE.LineSegments(
                new THREE.WireframeGeometry(geometry),
                new THREE.LineBasicMaterial({
                    color: '#3a4045',
                    transparent: true,
                    opacity: 0.1
                })
            );
            group.add(wireframe);
        }
        
        function onMouseDown(event) {
            isDragging = true;
            autoRotate = false;
            previousMousePosition = {
                x: event.clientX,
                y: event.clientY
            };
        }

        function onMouseUp(event) {
            isDragging = false;
            autoRotate = true;
            group.rotation.x = 0;
        }

        function onMouseMove(event) {
            if (isDragging) {
                const deltaMove = {
                    x: event.clientX - previousMousePosition.x,
                    y: event.clientY - previousMousePosition.y
                };

                group.rotation.y += deltaMove.x * 0.005;
                group.rotation.x += deltaMove.y * 0.005;

                previousMousePosition = {
                    x: event.clientX,
                    y: event.clientY
                };
            }
        }
        
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }
        
        function animate() {
            requestAnimationFrame(animate);
            
            if (autoRotate) {
                group.rotation.y += ROTATION_SPEED;
            }
            
            renderer.render(scene, camera);
        }
        
        init();
        animate();
    </script>
</body>
</html>

坐标点json源:https://files.cnblogs.com/files/Fooo/json.zip?t=1748420805&download=true