You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将文本入场动画叠加到Three.js全景背景动画上?

实现Three.js全景动画叠加文本入场效果

我帮你把文本入场动画和Three.js全景动画完美整合在一起了!之前合并失败大概率是因为DOM层级没处理好,或者动画初始化时机不对。下面是完整的可运行代码,直接就能用:

<!DOCTYPE html>
<html>
<head>
    <style>
        html, body { 
            margin: 0; 
            overflow: hidden; 
            font-family: Arial, sans-serif;
        }
        #container {
            position: absolute;
            width: 100vw;
            height: 100vh;
            z-index: 1;
        }
        .text-container {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 10;
            font-size: 3rem;
            color: #fff;
            text-shadow: 0 0 10px rgba(0,0,0,0.8);
        }
        .char {
            display: inline-block;
            opacity: 0;
            transform: translateY(20px);
            animation: fadeInUp 0.5s ease forwards;
        }
        @keyframes fadeInUp {
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        img { 
            width: 8vw; 
            position: absolute; 
            top: 0; 
            right: 0; 
            left: 0; 
            bottom: 0; 
            margin: auto; 
            z-index: 5;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <div class="text-container" id="animatedText">Hello World! Welcome to my Panorama</div>
    <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/planet2.png">

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
    <script>
        // Three.js全景动画代码
        var camera, scene, renderer; 
        var texture_placeholder, isUserInteracting = false, 
            onMouseDownMouseX = 0, onMouseDownMouseY = 0, 
            lon = 90, onMouseDownLon = 0, 
            lat = 0, onMouseDownLat = 0, 
            phi = 0, theta = 0, 
            target = new THREE.Vector3(); 

        init(); 
        animate(); 

        function init() { 
            var container, mesh; 
            container = document.getElementById('container'); 
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100); 
            scene = new THREE.Scene(); 

            texture_placeholder = document.createElement('canvas'); 
            texture_placeholder.width = 128; 
            texture_placeholder.height = 128; 

            var context = texture_placeholder.getContext('2d'); 
            context.fillStyle = 'rgb( 200, 200, 200 )'; 
            context.fillRect(0, 0, texture_placeholder.width, texture_placeholder.height); 

            var materials = [ 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space4.jpg'), // right 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space2.jpg'), // left 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space1.jpg'), // top 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space6.jpg'), // bottom 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space3.jpg'), // back 
                loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/1037366/space5.jpg') // front 
            ]; 

            mesh = new THREE.Mesh(new THREE.BoxGeometry(300, 300, 300, 7, 7, 7), new THREE.MultiMaterial(materials)); 
            mesh.scale.x = -1; 
            scene.add(mesh); 

            for (var i = 0, l = mesh.geometry.vertices.length; i < l; i++) { 
                var vertex = mesh.geometry.vertices[i]; 
                vertex.normalize(); 
                vertex.multiplyScalar(550); 
            } 

            renderer = new THREE.CanvasRenderer(); 
            renderer.setPixelRatio(window.devicePixelRatio); 
            renderer.setSize(window.innerWidth, window.innerHeight); 
            container.appendChild(renderer.domElement); 

            document.addEventListener('mousedown', onDocumentMouseDown, false); 
            document.addEventListener('mousemove', onDocumentMouseMove, false); 
            document.addEventListener('mouseup', onDocumentMouseUp, false); 
            document.addEventListener('touchstart', onDocumentTouchStart, false); 
            document.addEventListener('touchmove', onDocumentTouchMove, false); 
            window.addEventListener('resize', onWindowResize, false); 

            // 初始化文本动画
            initTextAnimation();
        } 

        function onWindowResize() { 
            camera.aspect = window.innerWidth / window.innerHeight; 
            camera.updateProjectionMatrix(); 
            renderer.setSize(window.innerWidth, window.innerHeight); 
        } 

        function loadTexture(path) { 
            var texture = new THREE.Texture(texture_placeholder); 
            var material = new THREE.MeshBasicMaterial({ map: texture, overdraw: 0.5 }); 
            var image = new Image(); 
            image.onload = function() { 
                texture.image = this; 
                texture.needsUpdate = true; 
            }; 
            image.src = path; 
            return material; 
        } 

        function onDocumentMouseDown(event) { 
            event.preventDefault(); 
            isUserInteracting = true; 
            onPointerDownPointerX = event.clientX; 
            onPointerDownPointerY = event.clientY; 
            onPointerDownLon = lon; 
            onPointerDownLat = lat; 
        } 

        function onDocumentMouseMove(event) { 
            if (isUserInteracting === true) { 
                lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon; 
                lat = (event.clientY - onPointerDownPointerY) * 0.1 + onPointerDownLat; 
            } 
        } 

        function onDocumentMouseUp(event) { 
            isUserInteracting = false; 
        } 

        function onDocumentTouchStart(event) { 
            if (event.touches.length == 1) { 
                event.preventDefault(); 
                onPointerDownPointerX = event.touches[0].pageX; 
                onPointerDownPointerY = event.touches[0].pageY; 
                onPointerDownLon = lon; 
                onPointerDownLat = lat; 
            } 
        } 

        function onDocumentTouchMove(event) { 
            if (event.touches.length == 1) { 
                event.preventDefault(); 
                lon = (onPointerDownPointerX - event.touches[0].pageX) * 0.1 + onPointerDownLon; 
                lat = (event.touches[0].pageY - onPointerDownPointerY) * 0.1 + onPointerDownLat; 
            } 
        } 

        function animate() { 
            requestAnimationFrame(animate); 
            update(); 
        } 

        function update() { 
            if (isUserInteracting === false) { 
                lon += 0.1; 
            } 
            lat = Math.max(-85, Math.min(85, lat)); 
            phi = THREE.Math.degToRad(90 - lat); 
            theta = THREE.Math.degToRad(lon); 

            target.x = 500 * Math.sin(phi) * Math.cos(theta); 
            target.y = 500 * Math.cos(phi); 
            target.z = 500 * Math.sin(phi) * Math.sin(theta); 

            camera.position.copy(target).negate(); 
            camera.lookAt(target); 

            renderer.render(scene, camera); 
        }

        // 文本入场动画代码
        function initTextAnimation() {
            const textElement = document.getElementById('animatedText');
            const text = textElement.textContent;
            textElement.innerHTML = '';
            
            // 将每个字符拆分成单独的span,添加动画延迟
            text.split('').forEach((char, index) => {
                const span = document.createElement('span');
                span.className = 'char';
                span.textContent = char === ' ' ? '\u00A0' : char; // 处理空格
                span.style.animationDelay = `${index * 0.1}s`; // 逐个字符延迟入场
                textElement.appendChild(span);
            });
        }
    </script>
</body>
</html>

关键实现细节说明:

  • DOM层级控制:给Three.js容器设置z-index:1,文本容器设置z-index:10,确保文本始终在全景动画上方;行星图片设置z-index:5,处于中间层级。
  • 文本动画拆分:把文本拆分成单个字符的<span>标签,通过animationDelay实现逐个字符入场的效果,和你参考的示例效果一致。
  • 初始化时机:在Three.js的init函数最后调用文本动画的初始化函数,确保DOM元素已经加载完成。
  • 响应式适配:保留了Three.js的窗口 resize 监听,确保全景动画始终铺满视口,文本也通过transform: translate(-50%, -50%)保持居中。

你可以根据自己的需求修改文本内容、字体大小、动画时长等参数,直接运行代码就能看到效果啦!

内容的提问来源于stack exchange,提问作者theenigma017

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:40:53