圣诞树H5粒子特效

这是一个h5的粒子特效圣诞树

如果出现编译后无法显示粒子特效,请切换网络环境,推荐使用手机热点加载粒子特效组件
手机端不支持显示,未做适配,建议电脑端edge或者Chrome打开
按A粒子变亮
按Y变黄绿色
按R树变红色(按R变不回去,只能按Y卡回去)
按S变大雪花(整体粒子会变亮,不建议和A同时开)
按D飘落的雪花会变无序
按C切视角
按空格旋转速度变快
右下角作者做了鼠标监控无法选中
网页缩放自行调整

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>(网页名)</title>
    <link href="https://fonts.googleapis.com/css2?family=Great+Vibes&family=Dancing+Script:wght@700&display=swap" rel="stylesheet">
    <style>
        body {
            margin: 0;
            height: 100vh;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #161616;
        }

        .message {
            position: fixed;
            left: 60px;
            top: 50%;
            transform: translateY(-50%);
            color: #fff;
            font-family: 'Brush Script MT', cursive;
            text-align: left;
            text-shadow: 0 0 15px rgba(255,255,255,0.6),
                         0 0 30px rgba(255,255,255,0.4),
                         0 0 45px rgba(255,255,255,0.2);
            pointer-events: none;
            z-index: 1000;
            transition: transform 0.3s ease-out;
        }

        .message h1 {
            font-size: 52px;
            margin: 0;
            opacity: 0;
            animation: fadeInLeft 2s ease-out forwards;
            letter-spacing: 2px;
        }

        .message p {
            font-size: 28px;
            margin: 20px 0;
            opacity: 0;
            animation: fadeInLeft 2s ease-out 1s forwards;
        }

        @keyframes fadeInLeft {
            from {
                opacity: 0;
                transform: translateX(-40px) scale(0.8);
            }
            to {
                opacity: 1;
                transform: translateX(0) scale(1);
            }
        }

        .author {
            position: fixed;
            right: 60px;
            bottom: 50px;
            color: #fff;
            font-family: 'Brush Script MT', cursive;
            text-align: right;
            text-shadow: 0 0 15px rgba(255,255,255,0.6),
                         0 0 30px rgba(255,255,255,0.4),
                         0 0 45px rgba(255,255,255,0.2);
            pointer-events: auto;
            z-index: 1000;
        }

        .author p {
            font-size: 26px;
            margin: 0;
            opacity: 0;
            animation: fadeIn 2s ease-out 1.5s forwards;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateX(40px) scale(0.8);
            }
            to {
                opacity: 1;
                transform: translateX(0) scale(1);
            }
        }
    </style>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/EffectComposer.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/RenderPass.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/ShaderPass.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/shaders/CopyShader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/shaders/LuminosityHighPassShader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/UnrealBloomPass.js"></script>

    <script>
        const { PI, sin, cos } = Math;
        const TAU = 2 * PI;

        const map = (value, sMin, sMax, dMin, dMax) => {
            return dMin + (value - sMin) / (sMax - sMin) * (dMax - dMin);
        };

        const rand = (max, min = 0) => min + Math.random() * (max - min);
        const randChoise = arr => arr[Math.floor(Math.random() * arr.length)];
        const polar = (ang, r = 1) => [r * cos(ang), r * sin(ang)];

        let scene, camera, renderer;
        let step = 0;
        const uniforms = {
            time: { type: "f", value: 0.0 },
            step: { type: "f", value: 0.0 }
        };

        const params = {
            exposure: 1,
            bloomStrength: 1.5,
            bloomThreshold: 0,
            bloomRadius: 0.7
        };

        let composer;
        const totalPoints = 4000;
        let treePoints;
        let groundPoints;

        let rotationSpeed = 0.002;
        let isSpacePressed = false;

        let isFlashing = false;
        let originalStrength, originalThreshold;

        let isDecorationsDancing = false;

        let isSnowBig = false;

        let cameraMode = 0;
        let isTreeGreen = false;

        document.addEventListener('keydown', (e) => {
            if (e.code === 'Space' && !isSpacePressed) {
                isSpacePressed = true;
                rotationSpeed = 0.01;
            }
            
            if (e.code === 'KeyA') {
                isFlashing = !isFlashing;
                if (isFlashing) {
                    originalStrength = params.bloomStrength;
                    originalThreshold = params.bloomThreshold;
                    params.bloomStrength = 3.0;
                    params.bloomThreshold = 0;
                } else {
                    params.bloomStrength = originalStrength;
                    params.bloomThreshold = originalThreshold;
                }
                composer.passes[1].strength = params.bloomStrength;
                composer.passes[1].threshold = params.bloomThreshold;
            }

            if (e.code === 'KeyR') {
                const geometry = treePoints.geometry;
                const colors = geometry.attributes.color.array;
                const color = new THREE.Color();
                
                for (let i = 0; i < colors.length; i += 3) {
                    if (i / colors.length > 0.1) {  // 只改变树叶的颜色
                        color.setHSL(0.0, 0.8, 0.5);  // 红色
                        colors[i] = color.r;
                        colors[i + 1] = color.g;
                        colors[i + 2] = color.b;
                    }
                }
                geometry.attributes.color.needsUpdate = true;
            }

            if (e.code === 'KeyD') {
                isDecorationsDancing = !isDecorationsDancing;
            }

            if (e.code === 'KeyS') {
                isSnowBig = !isSnowBig;
                scene.children.forEach(child => {
                    if (child.material && child.material.uniforms && child.material.uniforms.time) {
                        const sizes = child.geometry.attributes.size.array;
                        for (let i = 0; i < sizes.length; i++) {
                            sizes[i] = isSnowBig ? sizes[i] * 2 : sizes[i] / 2;
                        }
                        child.geometry.attributes.size.needsUpdate = true;
                    }
                });
            }

            if (e.code === 'KeyC') {
                cameraMode = (cameraMode + 1) % 3;
                switch(cameraMode) {
                    case 0:  // 正面视角
                        camera.position.set(0, -2, 42);
                        camera.rotation.set(0.12, 0, 0);
                        break;
                    case 1:  // 俯视角
                        camera.position.set(0, 50, 0);
                        camera.rotation.set(-Math.PI/2, 0, 0);
                        break;
                    case 2:  // 侧面视角
                        camera.position.set(42, -2, 0);
                        camera.rotation.set(0.12, Math.PI/2, 0);
                        break;
                }
            }

            if (e.code === 'KeyY') {
                const geometry = treePoints.geometry;
                const colors = geometry.attributes.color.array;
                const color = new THREE.Color();
                
                isTreeGreen = !isTreeGreen;
                
                for (let i = 0; i < colors.length; i += 3) {
                    const t = i / colors.length;
                    if (isTreeGreen) {
                        if (t < 0.1) {
                            color.setHSL(0.15, 0.8, 0.5);  // 树干颜色(黄色)
                        } else {
                            const brightness = map(t, 0.1, 1, 0.85, 0.95);
                            color.setHSL(0.3, 0.9, brightness);  // 树叶颜色(绿色)
                        }
                    } else {
                        if (t < 0.1) {
                            color.setHSL(0, 0, 0.3);  // 更淡的原始树干颜色
                        } else {
                            const brightness = map(t, 0.1, 1, 0.7, 0.9);
                            color.setHSL(0, 0, brightness);  // 原始树叶颜色
                        }
                    }
                    colors[i] = color.r;
                    colors[i + 1] = color.g;
                    colors[i + 2] = color.b;
                }
                geometry.attributes.color.needsUpdate = true;
            }
        });

        document.addEventListener('keyup', (e) => {
            if (e.code === 'Space') {
                isSpacePressed = false;
                rotationSpeed = 0.002;
            }
        });

        init();
        animate();

        function init() {
            scene = new THREE.Scene();
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);

            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            camera = new THREE.PerspectiveCamera(
                60,
                window.innerWidth / window.innerHeight,
                1,
                1000
            );
            camera.position.set(0, -2, 42);
            camera.rotation.set(0.12, 0, 0);

            scene.position.x = 0;
            scene.position.z = 1;

            addTree(scene, uniforms, 4000, [0, 0, 0]);
            addDecorations(scene, uniforms, 150);
            addSnow(scene, uniforms, 600);
            addPlane(scene, uniforms, 3000);

            const renderScene = new THREE.RenderPass(scene, camera);
            const bloomPass = new THREE.UnrealBloomPass(
                new THREE.Vector2(window.innerWidth, window.innerHeight),
                1.8,
                0.35,
                0.85
            );

            bloomPass.threshold = params.bloomThreshold;
            bloomPass.strength = params.bloomStrength;
            bloomPass.radius = params.bloomRadius;

            composer = new THREE.EffectComposer(renderer);
            composer.addPass(renderScene);
            composer.addPass(bloomPass);

            window.addEventListener('resize', onWindowResize, false);
        }

        function animate(time) {
            step = (step + 1) % 1000;
            uniforms.time.value = time;
            uniforms.step.value = step;
            
            if (treePoints) {
                treePoints.rotation.y += rotationSpeed;
                
                if (groundPoints) {
                    groundPoints.rotation.y = treePoints.rotation.y;
                }
            }
            
            if (isDecorationsDancing) {
                scene.children.forEach(child => {
                    if (child.material && child.material.uniforms && child.material.uniforms.time) {
                        const positions = child.geometry.attributes.position.array;
                        for (let i = 0; i < positions.length; i += 3) {
                            positions[i] += Math.sin(time * 0.001 + positions[i+1]) * 0.02;
                            positions[i+2] += Math.cos(time * 0.001 + positions[i+1]) * 0.02;
                        }
                        child.geometry.attributes.position.needsUpdate = true;
                    }
                });
            }
            
            composer.render();
            requestAnimationFrame(animate);
        }

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

        function addTree(scene, uniforms, totalPoints, treePosition, scale = 1) {
            const vertexShader = `
    attribute float mIndex;
    varying vec3 vColor;
    varying float opacity;
    
    float norm(float value, float min, float max ){
        return (value - min) / (max - min);
    }
    float lerp(float norm, float min, float max){
        return (max - min) * norm + min;
    }
    float map(float value, float sourceMin, float sourceMax, float destMin, float destMax){
        return lerp(norm(value, sourceMin, sourceMax), destMin, destMax);
    }
    void main() {
        vColor = color;
        vec3 p = position;
        vec4 mvPosition = modelViewMatrix * vec4( p, 1.0 );
        opacity = map(mvPosition.z , -200.0, 15.0, 0.0, 1.0);
        gl_PointSize = 4.0 * ( 100.0 / -mvPosition.z );
        gl_Position = projectionMatrix * mvPosition;
    }
    `;
            const fragmentShader = `
    varying vec3 vColor;
    varying float opacity;
    uniform sampler2D pointTexture;
    void main() {
        gl_FragColor = vec4( vColor, opacity );
        gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord ); 
    }
    `;
            const shaderMaterial = new THREE.ShaderMaterial({
                uniforms: {
                    pointTexture: {
                        value: new THREE.TextureLoader().load(`https://assets.codepen.io/3685267/spark1.png`)
                    }
                },
                vertexShader,
                fragmentShader,
                blending: THREE.AdditiveBlending,
                depthTest: false,
                transparent: true,
                vertexColors: true
            });
 
 
            const geometry = new THREE.BufferGeometry();
            const positions = [];
            const colors = [];
            const sizes = [];
            const phases = [];
            const mIndexs = [];
            const color = new THREE.Color();
 
            for (let i = 0; i < totalPoints; i++) {
                const randomT = Math.random();
                
                // 根据缩放调整高度和半径
                const y = map(randomT, 0, 1, -16 * scale, 20 * scale);
                const ang = map(randomT, 0, 1, 0, 6 * TAU) + TAU / 2 * (i % 2);
                
                let radius;
                if (randomT < 0.1) {
                    radius = 2 * scale;
                } else {
                    const treeTop = (randomT - 0.1) / 0.9;
                    radius = map(Math.pow(treeTop, 0.6), 0, 1, 15 * scale, 0);
                }
                const [z, x] = polar(ang, radius);

                const modifier = randomT < 0.1 ? 0.2 : map(randomT, 0.1, 1, 1.5, 0.5);
                positions.push(
                    x + rand(-0.15 * modifier, 0.15 * modifier) * scale,
                    y + rand(-0.1 * modifier, 0.1 * modifier) * scale,
                    z + rand(-0.15 * modifier, 0.15 * modifier) * scale
                );

                // 初始白色树干
                const t = map(i, 0, totalPoints, 0.0, 1.0);
                if (t < 0.1) {
                    // 树干颜色
                    color.setHSL(0, 0, 0.7);  // 白色树干
                } else {
                    // 树叶颜色
                    color.setHSL(0, 0, 0.9);  // 白色树叶
                }

                colors.push(color.r, color.g, color.b);
                phases.push(rand(1000));
                
                // 调整点的大小也要考虑缩放
                if (t < 0.1) {
                    sizes.push(2.5 * scale);
                } else {
                    const size = map(t, 0.1, 1, 2.2, 1.6) * scale;
                    sizes.push(size * (1 + rand(-0.1, 0.1)));
                }
                
                const mIndex = map(i, 0, totalPoints, 1.0, 0.0);
                mIndexs.push(mIndex);
            }
 
            geometry.setAttribute(
                "position",
                new THREE.Float32BufferAttribute(positions, 3).setUsage(
                    THREE.DynamicDrawUsage));
 
 
            geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
            geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));
            geometry.setAttribute("phase", new THREE.Float32BufferAttribute(phases, 1));
            geometry.setAttribute("mIndex", new THREE.Float32BufferAttribute(mIndexs, 1));
 
            const tree = new THREE.Points(geometry, shaderMaterial);
 
            const [px, py, pz] = treePosition;
 
            tree.position.x = px;
            tree.position.y = py;
            tree.position.z = pz;
 
            treePoints = tree;
            scene.add(tree);
        }
 
        function addSnow(scene, uniforms, totalPoints) {
            const vertexShader = `
    attribute float size;
    attribute float phase;
    attribute float phaseSecondary;
    varying vec3 vColor;
    varying float opacity;
    uniform float time;
    uniform float step;

    float norm(float value, float min, float max ){
        return (value - min) / (max - min);
    }
    float lerp(float norm, float min, float max){
        return (max - min) * norm + min;
    }
    float map(float value, float sourceMin, float sourceMax, float destMin, float destMax){
        return lerp(norm(value, sourceMin, sourceMax), destMin, destMax);
    }
    
    float random(float x) {
        return fract(sin(x) * 43758.5453);
    }

    void main() {
        float t = time * 0.0006;
        vColor = color;
        vec3 p = position;
        
        float yOffset = mod(phase + step, 1000.0);
        p.y = map(yOffset, 0.0, 1000.0, 25.0, -8.0);
        
        float xOffset = sin(t + phase) * (random(phase) * 2.0 + 0.5);
        float zOffset = cos(t + phaseSecondary) * (random(phaseSecondary) * 2.0 + 0.5);
        
        float rot = t * random(phase + phaseSecondary);
        p.x += xOffset + sin(rot) * random(phase);
        p.z += zOffset + cos(rot) * random(phaseSecondary);
        
        opacity = map(p.z, -150.0, 15.0, 0.0, 1.0);
        vec4 mvPosition = modelViewMatrix * vec4(p, 1.0);
        gl_PointSize = size * (100.0 / -mvPosition.z);
        gl_Position = projectionMatrix * mvPosition;
    }
    `;
 
            const fragmentShader = `
      uniform sampler2D pointTexture;
      varying vec3 vColor;
      varying float opacity;
      void main() {
       gl_FragColor = vec4( vColor, opacity );
       gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord ); 
      }
      `;
            function createSnowSet(sprite) {
                const totalPoints = 600;
                const shaderMaterial = new THREE.ShaderMaterial({
                    uniforms: {
                        ...uniforms,
                        pointTexture: {
                            value: new THREE.TextureLoader().load(sprite)
                        }
                    },
 
 
                    vertexShader,
                    fragmentShader,
                    blending: THREE.AdditiveBlending,
                    depthTest: false,
                    transparent: true,
                    vertexColors: true
                });
 
 
                const geometry = new THREE.BufferGeometry();
                const positions = [];
                const colors = [];
                const sizes = [];
                const phases = [];
                const phaseSecondaries = [];
 
                const color = new THREE.Color();
 
                for (let i = 0; i < totalPoints; i++) {
                    const [x, y, z] = [rand(100, -100), 0, rand(80, -400)];
                    positions.push(x);
                    positions.push(y);
                    positions.push(z);

                    if (Math.random() > 0.75) {
                        // 25%的大雪花
                        color.set(randChoise(["#ffffff", "#ffffff", "#fafaff"]));
                        sizes.push(rand(6, 4));
                    } else if (Math.random() > 0.5) {
                        // 25%的中等雪花
                        color.set(randChoise(["#ffffff", "#f8f8ff"]));
                        sizes.push(rand(4, 2.5));
                    } else {
                        // 50%的小雪花
                        color.set(randChoise(["#f1f6f9", "#eeeeee", "#e8e8e8"]));
                        sizes.push(rand(2.5, 1.5));
                    }
                    
                    colors.push(color.r, color.g, color.b);
                    phases.push(rand(1000));
                    phaseSecondaries.push(rand(1000));
                }
 
                geometry.setAttribute(
                    "position",
                    new THREE.Float32BufferAttribute(positions, 3));
 
                geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
                geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));
                geometry.setAttribute("phase", new THREE.Float32BufferAttribute(phases, 1));
                geometry.setAttribute(
                    "phaseSecondary",
                    new THREE.Float32BufferAttribute(phaseSecondaries, 1));
 
 
                const mesh = new THREE.Points(geometry, shaderMaterial);
 
                scene.add(mesh);
            }
            const sprites = [
                "https://assets.codepen.io/3685267/snowflake1.png",
                "https://assets.codepen.io/3685267/snowflake2.png",
                "https://assets.codepen.io/3685267/snowflake3.png",
                "https://assets.codepen.io/3685267/snowflake4.png",
                "https://assets.codepen.io/3685267/snowflake5.png",
                "https://assets.codepen.io/3685267/snowflake1.png",
                "https://assets.codepen.io/3685267/snowflake2.png",
                "https://assets.codepen.io/3685267/snowflake3.png",
                "https://assets.codepen.io/3685267/snowflake4.png"
            ];
 
            sprites.forEach(sprite => {
                createSnowSet(sprite);
            });
        }
 
        function addPlane(scene, uniforms, totalPoints) {
            const vertexShader = `
        attribute float size;
        attribute vec3 customColor;
        varying vec3 vColor;
        
        void main() {
            vColor = customColor;
            vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
            gl_PointSize = size * (300.0 / -mvPosition.z);
            gl_Position = projectionMatrix * mvPosition;
        }
    `;

            const fragmentShader = `
        uniform vec3 color;
        uniform sampler2D pointTexture;
        varying vec3 vColor;
        void main() {
            gl_FragColor = vec4(vColor, 1.0);
            gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord);
        }
    `;

            const shaderMaterial = new THREE.ShaderMaterial({
                uniforms: {
                    ...uniforms,
                    pointTexture: {
                        value: new THREE.TextureLoader().load(`https://assets.codepen.io/3685267/spark1.png`)
                    }
                },
                vertexShader,
                fragmentShader,
                blending: THREE.AdditiveBlending,
                depthTest: false,
                transparent: true,
                vertexColors: true
            });

            const geometry = new THREE.BufferGeometry();
            const positions = [];
            const colors = [];
            const sizes = [];
            const color = new THREE.Color();

            for (let i = 0; i < totalPoints; i++) {
                // 使用更大的范围和更自然的分布
                const angle = rand(0, TAU);
                const r = Math.sqrt(rand(0, 2500)); // 使用平方根分布
                const [x, z] = polar(angle, r);
                
                positions.push(x);
                positions.push(0);  // y 保持为 0
                positions.push(z);

                // 根据到中心的距离调整颜色和大小
                const distanceFromCenter = Math.sqrt(x * x + z * z);
                if (distanceFromCenter < 25) {  // 扩大中心域
                    color.set(randChoise(["#ffffff", "#fafaff", "#f8f8ff"]));
                    sizes.push(rand(2.2, 1.6));
                } else {
                    const brightness = map(distanceFromCenter, 25, 50, 0.95, 0.85);
                    color.setHSL(0, 0, brightness);
                    sizes.push(rand(1.6, 0.9));
                }

                colors.push(color.r, color.g, color.b);
            }

            geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
            geometry.setAttribute("customColor", new THREE.Float32BufferAttribute(colors, 3));
            geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));

            const plane = new THREE.Points(geometry, shaderMaterial);
            plane.position.y = -16;
            groundPoints = plane;
            scene.add(plane);
        }

        function addDecorations(scene, uniforms, totalPoints) {
            const vertexShader = `
                attribute float size;
                varying vec3 vColor;
                varying float opacity;
                uniform float time;
                
                void main() {
                    vColor = color;
                    vec3 p = position;
                    vec4 mvPosition = modelViewMatrix * vec4(p, 1.0);
                    opacity = 0.7;
                    float scale = 0.8 + sin(time * 0.001 + position.x) * 0.2;
                    gl_PointSize = size * scale * (100.0 / -mvPosition.z);
                    gl_Position = projectionMatrix * mvPosition;
                }
            `;

            const fragmentShader = `
                varying vec3 vColor;
                varying float opacity;
                uniform sampler2D pointTexture;
                void main() {
                    gl_FragColor = vec4(vColor, opacity);
                    gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord);
                }
            `;

            const shaderMaterial = new THREE.ShaderMaterial({
                uniforms: {
                    ...uniforms,
                    pointTexture: {
                        value: new THREE.TextureLoader().load(`https://assets.codepen.io/3685267/spark1.png`)
                    }
                },
                vertexShader,
                fragmentShader,
                blending: THREE.AdditiveBlending,
                depthTest: false,
                transparent: true,
                vertexColors: true
            });

            const geometry = new THREE.BufferGeometry();
            const positions = [];
            const colors = [];
            const sizes = [];
            const color = new THREE.Color();

            // 创建漂浮的光点
            const decorationPoints = 150;
            for (let i = 0; i < decorationPoints; i++) {
                const angle = (i / decorationPoints) * TAU;
                const radius = 15 + rand(-5, 5);
                const height = rand(-12, 22);
                const [x, z] = polar(angle, radius);

                positions.push(x, height, z);

                // 使用更丰富的颜色
                if (Math.random() > 0.7) {
                    // 金色
                    const hue = rand(0.1, 0.15);
                    const saturation = rand(0.7, 0.9);
                    const lightness = rand(0.6, 0.8);
                    color.setHSL(hue, saturation, lightness);
                } else if (Math.random() > 0.4) {
                    // 红色
                    const hue = rand(0.95, 1.0);
                    const saturation = rand(0.7, 0.9);
                    const lightness = rand(0.5, 0.7);
                    color.setHSL(hue, saturation, lightness);
                } else {
                    // 白色
                    const lightness = rand(0.8, 0.95);
                    color.setHSL(0, 0, lightness);
                }
                
                colors.push(color.r, color.g, color.b);
                sizes.push(rand(2.5, 4.5));
            }

            geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
            geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
            geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));

            const decorations = new THREE.Points(geometry, shaderMaterial);
            scene.add(decorations);
        }

        document.addEventListener('DOMContentLoaded', () => {
            const author = document.querySelector('.author');
            const maxDistance = 150;
            
            document.addEventListener('mousemove', (e) => {
                const authorRect = author.getBoundingClientRect();
                const authorCenter = {
                    x: authorRect.left + authorRect.width / 2,
                    y: authorRect.top + authorRect.height / 2
                };
                
                const authorDistance = Math.hypot(
                    e.clientX - authorCenter.x,
                    e.clientY - authorCenter.y
                );
                
                if (authorDistance < maxDistance) {
                    const angle = Math.atan2(
                        authorCenter.y - e.clientY,
                        authorCenter.x - e.clientX
                    );
                    const force = (maxDistance - authorDistance) / maxDistance;
                    const moveX = Math.cos(angle) * force * 60;
                    const moveY = Math.sin(angle) * force * 60;
                    
                    author.style.transform = `translate(${moveX}px, ${moveY}px)`;
                    author.style.transition = 'transform 0.2s ease-out';
                } else {
                    author.style.transform = 'none';
                    author.style.transition = 'transform 0.5s ease-out';
                }
            });
        });
    </script>

    <div class="message">
        <h1>Merry Christmas</h1>
        <p>--To:(被祝福人)</p>
    </div>

    <div class="author">
        <p>By: (作者名)</p>
    </div>
</body>
</html>



祝各位也有一个愉快的圣诞节!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值