three/文字爆裂效果

three/文字爆裂效果

皮蛋众妙真君.html

ini 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 文字粒子动画</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
        }
        canvas {
            display: block;
        }
        #loading-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-family: sans-serif;
            font-size: 24px;
            background-color: rgba(0, 0, 0, 0.7);
            z-index: 100;
        }
    </style>
</head>
<body>
    <div id="loading-overlay">
        <div id="loading-text">正在生成文字粒子...</div>
    </div>

    <script type="importmap">
        {
            "imports": {
                "three": "https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.module.js",
                "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/"
            }
        }
    </script>
    <script type="module">
        import * as THREE from 'three';

        let scene, camera, renderer;
        let textParticles, stars;
        let mouse = new THREE.Vector2(-100, -100);
        const clock = new THREE.Clock();
        const explosionDelay = 2.0; // 粒子组成文字后停留2秒
        let hasExploded = false;

        function init() {
            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 100;

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            document.body.appendChild(renderer.domElement);
            
            createStars();
            createTextParticles();

            window.addEventListener('resize', onWindowResize);
            window.addEventListener('mousemove', onMouseMove);
        }

        function createStars() {
            const starGeometry = new THREE.BufferGeometry();
            const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.5, transparent: true, opacity: 0.8 });
            const starVertices = [];
            for (let i = 0; i < 10000; i++) {
                const x = (Math.random() - 0.5) * 2000;
                const y = (Math.random() - 0.5) * 2000;
                const z = (Math.random() - 0.5) * 2000;
                starVertices.push(x, y, z);
            }
            starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
            stars = new THREE.Points(starGeometry, starMaterial);
            scene.add(stars);
        }

        // 核心改动:使用 Canvas 生成文字并采样粒子
        function createTextParticles() {
            const loadingOverlay = document.getElementById('loading-overlay');
            const loadingText = document.getElementById('loading-text');

            try {
                // 1. 创建 Canvas 并绘制文字
                const text = '皮蛋众妙真君';
                const fontSize = 120;
                const canvas = document.createElement('canvas');
                const context = canvas.getContext('2d');
                context.font = `bold ${fontSize}px "Microsoft YaHei", "SimHei", sans-serif`;
                const metrics = context.measureText(text);
                const textWidth = metrics.width;
                canvas.width = textWidth;
                canvas.height = fontSize * 1.2;
                context.font = `bold ${fontSize}px "Microsoft YaHei", "SimHei", sans-serif`;
                context.fillStyle = '#ffffff';
                context.fillText(text, 0, fontSize);

                // 2. 从 Canvas 采样像素数据
                const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
                const data = imageData.data;
                const particlePositions = [];
                const particleDensity = 4; // 采样密度,数值越小粒子越多

                for (let y = 0; y < canvas.height; y += particleDensity) {
                    for (let x = 0; x < canvas.width; x += particleDensity) {
                        const index = (y * canvas.width + x) * 4;
                        const alpha = data[index + 3];
                        if (alpha > 128) {
                            // 将 Canvas 坐标转换为居中的 3D 坐标
                            const pX = (x - canvas.width / 2) * 0.2;
                            const pY = -(y - canvas.height / 2) * 0.2;
                            particlePositions.push(new THREE.Vector3(pX, pY, 0));
                        }
                    }
                }

                if (particlePositions.length === 0) throw new Error('未能从Canvas生成粒子');

                // 3. 创建粒子系统
                const particleCount = particlePositions.length;
                const particleGeometry = new THREE.BufferGeometry();
                const positions = new Float32Array(particleCount * 3);
                const colors = new Float32Array(particleCount * 3);
                const velocities = new Float32Array(particleCount * 3);
                const initialPositions = new Float32Array(particleCount * 3); // 存储初始位置

                for (let i = 0; i < particleCount; i++) {
                    const i3 = i * 3;
                    const pos = particlePositions[i];
                    positions[i3] = pos.x;
                    positions[i3 + 1] = pos.y;
                    positions[i3 + 2] = pos.z;
                    
                    initialPositions[i3] = pos.x;
                    initialPositions[i3 + 1] = pos.y;
                    initialPositions[i3 + 2] = pos.z;

                    const color = new THREE.Color();
                    color.setHSL(Math.random(), 1.0, 0.5);
                    colors[i3] = color.r;
                    colors[i3 + 1] = color.g;
                    colors[i3 + 2] = color.b;
                    
                    // 初始速度为0
                    velocities[i3] = 0;
                    velocities[i3 + 1] = 0;
                    velocities[i3 + 2] = 0;
                }

                particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
                particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
                particleGeometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));
                particleGeometry.setAttribute('initialPosition', new THREE.BufferAttribute(initialPositions, 3));

                const particleMaterial = new THREE.PointsMaterial({
                    size: 1.0,
                    vertexColors: true,
                    blending: THREE.AdditiveBlending,
                    transparent: true,
                });

                textParticles = new THREE.Points(particleGeometry, particleMaterial);
                scene.add(textParticles);

                loadingOverlay.style.display = 'none';
            } catch (error) {
                console.error("创建文字粒子失败:", error);
                loadingText.innerText = '创建文字粒子失败';
            }
        }

        function animate() {
            requestAnimationFrame(animate);
            const elapsedTime = clock.getElapsedTime();

            if (stars) {
                stars.rotation.y += 0.0001;
            }

            if (textParticles) {
                if (elapsedTime > explosionDelay && !hasExploded) {
                    // 触发爆裂
                    const velocities = textParticles.geometry.attributes.velocity;
                    for (let i = 0; i < velocities.count; i++) {
                        const i3 = i * 3;
                        velocities.array[i3] = (Math.random() - 0.5) * 4;
                        velocities.array[i3+1] = (Math.random() - 0.5) * 4;
                        velocities.array[i3+2] = (Math.random() - 0.5) * 4;
                    }
                    velocities.needsUpdate = true;
                    hasExploded = true;
                }

                if (hasExploded) {
                    // 爆裂后的持续动画
                    const positions = textParticles.geometry.attributes.position;
                    const velocities = textParticles.geometry.attributes.velocity;
                    
                    for (let i = 0; i < positions.count; i++) {
                        const i3 = i * 3;
                        const px = positions.getX(i);
                        const py = positions.getY(i);
                        const dx = mouse.x - px;
                        const dy = mouse.y - py;
                        const distSq = dx * dx + dy * dy;
                        const mouseForceRadius = 30;

                        if (distSq < mouseForceRadius * mouseForceRadius) {
                            const dist = Math.sqrt(distSq);
                            const force = (mouseForceRadius - dist) / mouseForceRadius;
                            velocities.array[i3] -= dx / dist * force * 0.5;
                            velocities.array[i3 + 1] -= dy / dist * force * 0.5;
                        }

                        // 粒子随机扰动
                        velocities.array[i3] += (Math.random() - 0.5) * 0.01;
                        velocities.array[i3 + 1] += (Math.random() - 0.5) * 0.01;
                        velocities.array[i3 + 2] += (Math.random() - 0.5) * 0.01;

                        // 更新位置
                        positions.array[i3] += velocities.array[i3];
                        positions.array[i3 + 1] += velocities.array[i3 + 1];
                        positions.array[i3 + 2] += velocities.array[i3 + 2];
                        
                        // 速度衰减
                        velocities.array[i3] *= 0.97;
                        velocities.array[i3 + 1] *= 0.97;
                        velocities.array[i3 + 2] *= 0.97;
                    }
                    positions.needsUpdate = true;
                }
            }

            renderer.render(scene, camera);
        }

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

        function onMouseMove(event) {
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
            const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
            vector.unproject(camera);
            const dir = vector.sub(camera.position).normalize();
            const distance = -camera.position.z / dir.z;
            const pos = camera.position.clone().add(dir.multiplyScalar(distance));
            mouse.x = pos.x;
            mouse.y = pos.y;
        }

        init();
        animate();
    </script>
</body>
</html>
相关推荐
前端人类学2 天前
构筑数字夜空:Three.js 建筑群灯光特效全解析
javascript·three.js
柳杉4 天前
使用three.js搭建3d隧道监测-3
前端·javascript·three.js
三维搬砖者5 天前
06Threejs电影拍摄角度-第三章:搭建场景 - 初始化环境
three.js
sixgod_h5 天前
Threejs源码系列- renderer/webgl
three.js
陈小峰_iefreer8 天前
使用Stone 3D快速制作第一人称视角在线小游戏
游戏引擎·元宇宙·three.js·web3d
刘皇叔code13 天前
Three.js后处理UnrealBloomPass的分析
webgl·three.js
IT码农-爱吃辣条15 天前
Three.js 初级教程大全
开发语言·javascript·three.js
孪创启航营15 天前
数字孪生二维热力图制作,看这篇文章就够了!
前端·three.js·cesium
Mapmost16 天前
【BIM+GIS】BIM数据格式解析&与数字孪生适配的关键挑战
前端·vue.js·three.js