html5 canvas 绘制下雪效果

1.html5 canvas 绘制下雪效果

展示效果:

Video_2025-12-27_152202_converted

ai写代码为主,人工辅助修改:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas 下雪效果 - 透明背景</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: url(http://www.jnqianle.cn/upload/logo/content/202512/23/2303f83d-bb97-4153-8e0d-bc515722327d.jpg);
            font-family: 'Segoe UI', Arial, sans-serif;
            color: #e0e0ff;
            padding: 20px;
            overflow: hidden;
        }
        
        .container {
            display: flex;
            flex-direction: column;
            align-items: center;
            max-width: 1200px;
            width: 100%;
            gap: 30px;
            z-index: 10;
        }
        
        .header {
            text-align: center;
            padding: 25px 30px;
            background: rgba(15, 25, 60, 0.7);
            border-radius: 20px;
            width: 100%;
            max-width: 800px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            backdrop-filter: blur(5px);
        }
        
        h1 {
            font-size: 2.8rem;
            margin-bottom: 15px;
            background: linear-gradient(90deg, #ffffff, #a8d0ff);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            text-shadow: 0 2px 10px rgba(255, 255, 255, 0.2);
        }
        
        .subtitle {
            font-size: 1.2rem;
            opacity: 0.9;
            max-width: 700px;
            line-height: 1.6;
            margin: 0 auto;
            color: #b0c8ff;
        }
        
        .demo-area {
            display: flex;
            flex-wrap: wrap;
            gap: 30px;
            width: 100%;
            justify-content: center;
        }
        
        .canvas-container {
            position: relative;
            width: 800px;
            height: 500px;
            border-radius: 16px;
            overflow: hidden;
            /* box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
            border: 2px solid rgba(255, 255, 255, 0.15); */
        }
        
        .canvas-wrapper {
            width: 100%;
            height: 100%;
            /* background: 
                linear-gradient(rgba(30, 60, 114, 0.8), rgba(30, 60, 114, 0.8)),
                url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%231a1a2e"/><path d="M0,0 L100,100 M100,0 L0,100" stroke="%232a2a4a" stroke-width="1"/></svg>');
            */
                display: flex;
            justify-content: center;
            align-items: center;
        }
        
        canvas {
            display: block;
            width: 100%;
            height: 100%;
        }
        
        .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 25px;
            background: rgba(20, 30, 70, 0.8);
            padding: 25px;
            border-radius: 16px;
            width: 100%;
            max-width: 800px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(5px);
        }
        
        .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            min-width: 150px;
        }
        
        .control-title {
            font-size: 1.1rem;
            margin-bottom: 12px;
            color: #a8c8ff;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .control-title i {
            color: #a8d0ff;
        }
        
        input[type="range"] {
            width: 180px;
            height: 8px;
            -webkit-appearance: none;
            background: rgba(30, 40, 80, 0.8);
            border-radius: 4px;
            outline: none;
        }
        
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 22px;
            height: 22px;
            border-radius: 50%;
            background: #a8d0ff;
            cursor: pointer;
            box-shadow: 0 0 10px rgba(168, 208, 255, 0.8);
            border: 2px solid #ffffff;
        }
        
        .value-display {
            margin-top: 8px;
            font-size: 1.1rem;
            color: #a8d0ff;
            font-weight: bold;
            min-width: 60px;
            text-align: center;
        }
        
        .buttons {
            display: flex;
            justify-content: center;
            gap: 15px;
            width: 100%;
            margin-top: 10px;
        }
        
        button {
            padding: 14px 28px;
            background: linear-gradient(90deg, #4a7dff, #6ba8ff);
            border: none;
            border-radius: 10px;
            color: white;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 6px 20px rgba(74, 125, 255, 0.3);
        }
        
        button:hover {
            transform: translateY(-4px);
            box-shadow: 0 10px 25px rgba(74, 125, 255, 0.5);
        }
        
        button:active {
            transform: translateY(0);
        }
        
        .info {
            background: rgba(20, 30, 70, 0.8);
            border-radius: 16px;
            padding: 25px;
            margin-top: 10px;
            width: 100%;
            max-width: 800px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(5px);
        }
        
        .info-title {
            font-size: 1.3rem;
            color: #a8c8ff;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .info-content {
            line-height: 1.7;
            color: #b0c8ff;
        }
        
        .transparent-demo {
            margin-top: 30px;
            padding: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 12px;
            text-align: center;
            max-width: 800px;
        }
        
        .transparent-demo p {
            margin-bottom: 15px;
            color: #c8d8ff;
        }
        
        .footer {
            text-align: center;
            margin-top: 30px;
            color: #a8c8ff;
            font-size: 0.95rem;
            opacity: 0.8;
            max-width: 800px;
        }
        
        @media (max-width: 900px) {
            .canvas-container {
                width: 95%;
                height: 400px;
            }
            
            .controls {
                flex-direction: column;
                align-items: center;
            }
            
            h1 {
                font-size: 2.2rem;
            }
        }
    </style>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
    <div class="container">
        <div class="header">
            <h1><i class="fas fa-snowflake"></i> Canvas 下雪效果</h1>
            <p class="subtitle">具有透明背景的缓慢下雪动画,雪花具有随机大小、速度和轻微摆动效果。Canvas背景完全透明,可叠加在任何背景上。</p>
        </div>
        
        <div class="demo-area">
            <div class="canvas-container">
                <div class="canvas-wrapper">
                    <canvas id="snowCanvas"></canvas>
                </div>
            </div>
        </div>
        
        <div class="controls">
            <div class="control-group">
                <div class="control-title">
                    <i class="fas fa-wind"></i>
                    <span>雪花数量</span>
                </div>
                <input type="range" id="countSlider" min="50" max="500" step="10" value="200">
                <div class="value-display" id="countValue">200</div>
            </div>
            
            <div class="control-group">
                <div class="control-title">
                    <i class="fas fa-tachometer-alt"></i>
                    <span>下落速度</span>
                </div>
                <input type="range" id="speedSlider" min="0.5" max="3" step="0.1" value="1.5">
                <div class="value-display" id="speedValue">1.5</div>
            </div>
            
            <div class="control-group">
                <div class="control-title">
                    <i class="fas fa-expand-alt"></i>
                    <span>雪花大小</span>
                </div>
                <input type="range" id="sizeSlider" min="1" max="10" step="0.5" value="4">
                <div class="value-display" id="sizeValue">4.0</div>
            </div>
            
            <div class="control-group">
                <div class="control-title">
                    <i class="fas fa-random"></i>
                    <span>摆动幅度</span>
                </div>
                <input type="range" id="swingSlider" min="0" max="5" step="0.2" value="1.5">
                <div class="value-display" id="swingValue">1.5</div>
            </div>
        </div>
        
        <div class="buttons">
            <button id="restartBtn">
                <i class="fas fa-redo"></i> 重新开始
            </button>
            <button id="pauseBtn">
                <i class="fas fa-pause"></i> 暂停/继续
            </button>
            <button id="windBtn">
                <i class="fas fa-wind"></i> 风向切换
            </button>
        </div>
        
        <div class="transparent-demo">
            <p><strong>透明背景演示:</strong> Canvas背景是完全透明的,这里看到的背景是为了演示而添加的。在实际使用中,下雪效果可以叠加在任何背景上。</p>
            <button id="toggleBgBtn">
                <i class="fas fa-eye"></i> 切换演示背景
            </button>
        </div>
        
        <div class="info">
            <div class="info-title">
                <i class="fas fa-info-circle"></i>
                <span>下雪效果说明</span>
            </div>
            <div class="info-content">
                <p>此Canvas动画模拟真实的下雪效果,具有以下特性:</p>
                <ul style="padding-left: 20px; margin-top: 10px;">
                    <li><strong>透明背景:</strong>Canvas背景完全透明,可以叠加在任何页面上。</li>
                    <li><strong>随机参数:</strong>每个雪花的大小、下落速度和摆动幅度都是随机的。</li>
                    <li><strong>自然运动:</strong>雪花不仅有垂直下落,还有轻微的左右摆动,模拟真实雪花飘落。</li>
                    <li><strong>循环效果:</strong>雪花落到底部后会重置到顶部,形成连续下雪效果。</li>
                    <li><strong>交互控制:</strong>可以实时调整雪花数量、速度、大小和摆动幅度。</li>
                </ul>
            </div>
        </div>
        
        <div class="footer">
            <p>使用HTML5 Canvas实现 | 透明背景下雪效果 | 可自定义参数</p>
        </div>
    </div>

    <script>
        // 获取Canvas元素和上下文
        const canvas = document.getElementById('snowCanvas');
        const ctx = canvas.getContext('2d');
        
        // 设置Canvas尺寸
        function resizeCanvas() {
            canvas.width = canvas.clientWidth;
            canvas.height = canvas.clientHeight;
        }
        
        // 初始设置
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
        
        // 雪花数组
        let snowflakes = [];
        
        // 动画参数
        let animationParams = {
            snowflakeCount: 200,
            speed: 1.5,
            size: 4.0,
            swing: 1.5,
            isRunning: true,
            windDirection: 1, // 1表示轻微向右,-1表示轻微向左
            windStrength: 0.5,
            backgroundColor: true // 演示背景开关
        };
        
        // 获取DOM元素
        const countSlider = document.getElementById('countSlider');
        const speedSlider = document.getElementById('speedSlider');
        const sizeSlider = document.getElementById('sizeSlider');
        const swingSlider = document.getElementById('swingSlider');
        const restartBtn = document.getElementById('restartBtn');
        const pauseBtn = document.getElementById('pauseBtn');
        const windBtn = document.getElementById('windBtn');
        const toggleBgBtn = document.getElementById('toggleBgBtn');
        
        const countValue = document.getElementById('countValue');
        const speedValue = document.getElementById('speedValue');
        const sizeValue = document.getElementById('sizeValue');
        const swingValue = document.getElementById('swingValue');
        
        // 更新显示值
        countSlider.addEventListener('input', function() {
            countValue.textContent = this.value;
            animationParams.snowflakeCount = parseInt(this.value);
            initSnowflakes();
        });
        
        speedSlider.addEventListener('input', function() {
            speedValue.textContent = this.value;
            animationParams.speed = parseFloat(this.value);
        });
        
        sizeSlider.addEventListener('input', function() {
            sizeValue.textContent = this.value;
            animationParams.size = parseFloat(this.value);
        });
        
        swingSlider.addEventListener('input', function() {
            swingValue.textContent = this.value;
            animationParams.swing = parseFloat(this.value);
        });
        
        // 按钮事件
        restartBtn.addEventListener('click', function() {
            initSnowflakes();
        });
        
        pauseBtn.addEventListener('click', function() {
            animationParams.isRunning = !animationParams.isRunning;
            if (animationParams.isRunning) {
                pauseBtn.innerHTML = '<i class="fas fa-pause"></i> 暂停动画';
            } else {
                pauseBtn.innerHTML = '<i class="fas fa-play"></i> 继续动画';
            }
        });
        
        windBtn.addEventListener('click', function() {
            animationParams.windDirection *= -1;
            if (animationParams.windDirection === 1) {
                windBtn.innerHTML = '<i class="fas fa-wind"></i> 风向切换 (右)';
            } else {
                windBtn.innerHTML = '<i class="fas fa-wind"></i> 风向切换 (左)';
            }
        });
        
        toggleBgBtn.addEventListener('click', function() {
            animationParams.backgroundColor = !animationParams.backgroundColor;
            const canvasWrapper = document.querySelector('.canvas-wrapper');
            if (animationParams.backgroundColor) {
                canvasWrapper.style.background = 
                    'linear-gradient(rgba(30, 60, 114, 0.8), rgba(30, 60, 114, 0.8)), ' +
                    'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%231a1a2e"/><path d="M0,0 L100,100 M100,0 L0,100" stroke="%232a2a4a" stroke-width="1"/></svg>\')';
                toggleBgBtn.innerHTML = '<i class="fas fa-eye"></i> 切换演示背景 (开)';
            } else {
                canvasWrapper.style.background = 'transparent';
                toggleBgBtn.innerHTML = '<i class="fas fa-eye"></i> 切换演示背景 (关)';
            }
        });
        
        // 雪花类
        class Snowflake {
            constructor() {
                this.reset();
            }
            
            reset() {
                // 随机位置
                this.x = Math.random() * canvas.width;
                this.y = Math.random() * canvas.height;
                
                // 随机大小 (基于基础大小)
                this.radius = animationParams.size * (0.5 + Math.random() * 0.5);
                
                // 随机速度 (基于基础速度)
                this.speed = animationParams.speed * (0.5 + Math.random() * 0.5);
                
                // 随机摆动幅度
                this.swingAmplitude = animationParams.swing * (0.5 + Math.random() * 0.5);
                
                // 随机摆动速度
                this.swingSpeed = 0.01 + Math.random() * 0.02;
                
                // 摆动相位
                this.swingPhase = Math.random() * Math.PI * 2;
                
                // 随机透明度
                this.alpha = 0.5 + Math.random() * 0.5;
                
                // 随机形状变化
                this.shapeVariant = Math.floor(Math.random() * 3);
            }
            
            update() {
                // 垂直下落
                this.y += this.speed;
                
                // 水平摆动 (正弦运动)
                this.swingPhase += this.swingSpeed;
                this.x += Math.sin(this.swingPhase) * this.swingAmplitude * 0.5;
                
                // 风向影响
                this.x += animationParams.windDirection * animationParams.windStrength;
                
                // 如果雪花超出边界,重置到顶部
                if (this.y > canvas.height + this.radius) {
                    this.reset();
                    this.y = -this.radius;
                }
                
                // 水平边界处理
                if (this.x > canvas.width + this.radius) {
                    this.x = -this.radius;
                } else if (this.x < -this.radius) {
                    this.x = canvas.width + this.radius;
                }
            }
            
            draw() {
                ctx.save();
                ctx.globalAlpha = this.alpha;
                ctx.fillStyle = 'white';
                
                // 绘制不同形状的雪花
                if (this.shapeVariant === 0) {
                    // 圆形雪花
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                    ctx.fill();
                } else if (this.shapeVariant === 1) {
                    // 六角形雪花
                    ctx.beginPath();
                    for (let i = 0; i < 6; i++) {
                        const angle = (i * Math.PI) / 3;
                        const px = this.x + Math.cos(angle) * this.radius;
                        const py = this.y + Math.sin(angle) * this.radius;
                        
                        if (i === 0) {
                            ctx.moveTo(px, py);
                        } else {
                            ctx.lineTo(px, py);
                        }
                    }
                    ctx.closePath();
                    ctx.fill();
                } else {
                    // 星形雪花
                    ctx.beginPath();
                    for (let i = 0; i < 5; i++) {
                        const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2;
                        const px = this.x + Math.cos(angle) * this.radius;
                        const py = this.y + Math.sin(angle) * this.radius;
                        
                        if (i === 0) {
                            ctx.moveTo(px, py);
                        } else {
                            ctx.lineTo(px, py);
                        }
                        
                        // 内角
                        const innerAngle = angle + Math.PI / 5;
                        const innerRadius = this.radius * 0.5;
                        const innerPx = this.x + Math.cos(innerAngle) * innerRadius;
                        const innerPy = this.y + Math.sin(innerAngle) * innerRadius;
                        ctx.lineTo(innerPx, innerPy);
                    }
                    ctx.closePath();
                    ctx.fill();
                }
                
                ctx.restore();
            }
        }
        
        // 初始化雪花
        function initSnowflakes() {
            snowflakes = [];
            for (let i = 0; i < animationParams.snowflakeCount; i++) {
                snowflakes.push(new Snowflake());
            }
        }
        
        // 绘制下雪效果
        function drawSnow() {
            // 清空Canvas - 使用透明色清除以实现透明背景
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // 更新和绘制每个雪花
            if (animationParams.isRunning) {
                for (let i = 0; i < snowflakes.length; i++) {
                    snowflakes[i].update();
                    snowflakes[i].draw();
                }
            } else {
                // 如果暂停,只绘制不更新
                for (let i = 0; i < snowflakes.length; i++) {
                    snowflakes[i].draw();
                }
            }
            
            // 请求下一帧动画
            requestAnimationFrame(drawSnow);
        }
        
        // 初始化并开始动画
        initSnowflakes();
        drawSnow();
        
        // 添加键盘控制
        document.addEventListener('keydown', function(e) {
            switch(e.key) {
                case ' ':
                    // 空格键暂停/继续
                    animationParams.isRunning = !animationParams.isRunning;
                    if (animationParams.isRunning) {
                        pauseBtn.innerHTML = '<i class="fas fa-pause"></i> 暂停动画';
                    } else {
                        pauseBtn.innerHTML = '<i class="fas fa-play"></i> 继续动画';
                    }
                    break;
                case 'r':
                case 'R':
                    // R键重新开始
                    initSnowflakes();
                    break;
                case 'w':
                case 'W':
                    // W键切换风向
                    animationParams.windDirection *= -1;
                    if (animationParams.windDirection === 1) {
                        windBtn.innerHTML = '<i class="fas fa-wind"></i> 风向切换 (右)';
                    } else {
                        windBtn.innerHTML = '<i class="fas fa-wind"></i> 风向切换 (左)';
                    }
                    break;
            }
        });
    </script>
</body>
</html>

 

更多:

使用Canvas实现下雪功能

html5 canvas 绘制节日礼花效果

html5 canvas 路径渲染_画线

html5 canvas 文本渲染

posted @ 2025-12-27 15:26  天马3798  阅读(6)  评论(0)    收藏  举报