如何将文本入场动画叠加到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
相关产品推荐
相关产品推荐

