当前位置: 首页 > article >正文

HTML5 Canvas和JavaScript的3D粒子星系效果

HTML部分

  • 基本结构包括<html><head>, 和 <body>标签。
  • <title>标签设置了页面标题为“优化版3D粒子星系”。
  • <style>块定义了一些基本样式:
    • body:无边距,隐藏滚动条,黑色背景,禁用触摸动作以提高性能。
    • canvas:设置鼠标指针为移动图标(暗示用户可以拖动)。

JavaScript部分

配置与类定义
  1. 配置对象 (config):

    • 定义了粒子数量、引力强度、连线范围等参数,用于调整粒子系统的行为。
  2. Vector3 类

    • 使用Float32Array存储三维向量数据,提供对x, y, z分量的访问方法,有助于提升计算性能。
  3. ParticleSystem 类

    • 构造函数:初始化画布、上下文、粒子数组和其他必要属性。
    • init 方法:初始化粒子系统,包括粒子生成、事件监听器绑定和动画启动。
    • resize 方法:处理窗口大小变化时的重绘逻辑,使用防抖技术避免频繁重绘。
    • draw 方法:核心渲染逻辑,利用离屏Canvas进行绘制以提高性能,并对每个粒子进行位置更新和投影计算。
    • animate 方法:动画循环,调用draw方法并请求下一帧动画。
    • destroy 方法:清理资源,取消动画帧请求并移除事件监听器。
关键特性与优化点
  1. 性能优化

    • 减少粒子数量 (PARTICLE_COUNT: 150) 和降低引力强度 (GRAVITY: 0.3) 以减少计算负荷。
    • 使用Float32Array代替普通数组来存储数值,提高数值计算效率。
    • 禁用透明通道 (alpha: false) 提高绘图性能。
    • 利用离屏Canvas (bufferCanvas) 进行绘制,减少主Canvas的重绘次数。
    • 使用防抖处理窗口大小变化 (resize方法),防止频繁触发重绘。
  2. 用户体验优化

    • 支持鼠标和触摸设备的交互,使粒子系统在不同设备上都能良好工作。
    • 页面隐藏时自动暂停动画 (visibilitychange事件监听),节省资源。
  3. 视觉效果

    • 动态计算每个粒子的位置、角度和距离,模拟出3D旋转效果。
    • 根据时间动态调整粒子颜色 (color: hsl(${Math.random()*360}, 70%, 50%)),增加视觉层次感。
    • 实现粒子的缩放和投影效果,增强立体感。

<!DOCTYPE html>
<html>
<head>
    <title>优化版3D粒子星系</title>
    <style>
        body { 
            margin: 0; 
            overflow: hidden; 
            background: #000;
            touch-action: none;
        }
        canvas { 
            cursor: move;
            /* 移除滤镜提升性能 */
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>

    <script>
        // 性能优化配置
        const config = {
            PARTICLE_COUNT: 150,  // 减少粒子数量
            GRAVITY: 0.3,         // 降低引力强度
            LINE_THRESHOLD: 80,   // 缩小连线范围
            ZOOM_SPEED: 0.0005,
            ROTATION_SPEED: 0.0001,
            COLOR_CYCLE: 0.0002
        };

        // 使用Float32Array提升计算性能
        class Vector3 {
            constructor() {
                this.data = new Float32Array(3);
            }
            
            set x(v) { this.data[0] = v; }
            set y(v) { this.data[1] = v; }
            set z(v) { this.data[2] = v; }
            get x() { return this.data[0]; }
            get y() { return this.data[1]; }
            get z() { return this.data[2]; }
        }

        class ParticleSystem {
            constructor() {
                this.canvas = document.getElementById('canvas');
                this.ctx = this.canvas.getContext('2d', { alpha: false }); // 关闭透明通道
                this.particles = [];
                this.core = new Vector3();
                this.mouse = new Vector3();
                this.animationFrame = null;
                this.init();
            }

            init() {
                // 初始化粒子
                for(let i=0; i<config.PARTICLE_COUNT; i++) {
                    this.particles.push({
                        pos: new Vector3(),
                        vel: new Vector3(),
                        angle: Math.PI * 2 * Math.random(),
                        distance: 100 + Math.random() * 400,
                        mass: 0.5 + Math.random(),
                        color: `hsl(${Math.random()*360}, 70%, 50%)`
                    });
                }

                // 节流处理
                this.resize();
                window.addEventListener('resize', () => this.resize());
                this.canvas.addEventListener('mousemove', e => this.handleMove(e));
                this.canvas.addEventListener('touchmove', e => this.handleTouch(e), {passive: true});

                this.animate();
            }

            // 使用防抖优化resize
            resize = () => {
                cancelAnimationFrame(this.animationFrame);
                this.canvas.width = window.innerWidth;
                this.canvas.height = window.innerHeight;
                this.core.x = this.canvas.width/2;
                this.core.y = this.canvas.height/2;
                this.animationFrame = requestAnimationFrame(this.animate);
            }

            draw() {
                // 使用离屏Canvas提升渲染性能
                if(!this.bufferCanvas) {
                    this.bufferCanvas = document.createElement('canvas');
                    this.bufferCtx = this.bufferCanvas.getContext('2d');
                    this.bufferCanvas.width = this.canvas.width;
                    this.bufferCanvas.height = this.canvas.height;
                }

                const ctx = this.bufferCtx;
                ctx.fillStyle = 'rgb(0,0,0)';
                ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

                const time = performance.now();
                this.particles.forEach(particle => {
                    // 优化后的运动计算
                    particle.angle += config.ROTATION_SPEED / particle.mass;
                    const x = Math.cos(particle.angle) * particle.distance;
                    const z = Math.sin(particle.angle) * particle.distance;
                    const y = Math.sin(time*0.001 + particle.angle) * 30;

                    // 投影计算
                    const scale = 150 / (150 + z);
                    const px = x * scale + this.core.x;
                    const py = y * scale + this.core.y;

                    // 绘制优化
                    ctx.beginPath();
                    ctx.arc(px, py, 1.5, 0, Math.PI*2);
                    ctx.fillStyle = particle.color;
                    ctx.fill();
                });

                // 单次绘制到主Canvas
                this.ctx.drawImage(this.bufferCanvas, 0, 0);
            }

            animate = () => {
                this.draw();
                this.animationFrame = requestAnimationFrame(this.animate);
            }

            // 添加销毁方法
            destroy() {
                cancelAnimationFrame(this.animationFrame);
                window.removeEventListener('resize', this.resize);
            }
        }

        const system = new ParticleSystem();
        // 页面隐藏时自动暂停
        document.addEventListener('visibilitychange', () => {
            if(document.hidden) system.destroy();
        });
    </script>
</body>
</html>


http://www.kler.cn/a/514464.html

相关文章:

  • Element使用表单重置如果不使用prop,重置无法生效
  • NextJs - ServerAction获取文件并处理Excel
  • Flink 使用 Kafka 作为数据源时遇到了偏移量提交失败的问题
  • golang接口
  • AI Agent:AutoGPT的使用方法
  • Linux TCP 之 RTT 采集与 RTO 计算
  • 25/1/22 算法笔记<ROS2> TF变换
  • 从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CICD)
  • Python中采用.add_subplot绘制子图的方法简要举例介绍
  • NIO 和 Netty 在 Spring Boot 中的集成与使用
  • mapbox加载geojson,鼠标移入改变颜色,设置样式,vue中使用方法
  • 【docker-1】快速入门docker
  • java 根据前端传回的png图片数组,后端加水印加密码生成pdf,返回给前端
  • ELK介绍
  • 前沿技术趋势洞察:2024年技术的崭新篇章与未来走向!
  • (2)STM32 USB设备开发-USB虚拟串口
  • Android SystemUI——系统快捷设置面板(十三)
  • HTML<form>标签
  • suctf2025
  • Java 中 final 关键字的奥秘
  • 锐捷路由器网关RG-NBR6135-E和锐捷交换机 Ruijie Reyee RG-ES224GC 电脑登录web方法
  • IDEA导入Maven工程不识别pom.xml
  • 5G/4G+北斗三号水利遥测终端机RTU-打造水利工程的智能核心
  • Azure面试
  • PHP语言的语法糖
  • Java 设计模式 二 单例模式 (Singleton Pattern)