手把手教你系列 - Particle粒子特效(完整代码部分)

本着瞎折腾的学习态度,在闲暇之余就是要搞点奇奇怪怪的事情。文中如有哪不对的地方,还请大家指出。

本文项目github地址:https://github.com/SmallStoneSK/particle-effect

本文原创转载地址:https://blog.csdn.net/u013588817/article/details/78056140


index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Particle 粒子特效</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
        html, body {
            width: 100%;
            height: 100%;
            background-color: #292d35;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script src="ParticleEffect.js"></script>
    <script>
        window.onload = function() {
            ParticleEffect.run();
            // test();
        };
        // 实时获取窗口大小 和 从缓存中获取窗口大小 的性能对比
        function test() {
            var cache = {width: 1024, height: 780};
            function test(executeFunc, times) {
                var start, end, num = times;
                start = new Date();
                while(times--) {
                    executeFunc();
                }
                end = new Date();
                console.log(executeFunc.name + ' executes ' + num + 'times and takes ' + (end.getTime() - start.getTime()) / 1000 + 's.');
            }
            function getWindowSizeRealTime() {
                return {
                    width: window.innerWidth || document.documentElement.clientWidth,
                    height: window.innerHeight || document.documentElement.clientHeight
                };
            }
            function getWindowSizeFromCache() {
                return cache;
            }
            [1000, 10000, 100000, 1000000].forEach(function(times) {
                test(getWindowSizeRealTime, times);
                test(getWindowSizeFromCache, times);
            });
        }
    </script>
</body>
</html>

ParticleEffect.js

// 粒子特效
var ParticleEffect = {
    ctx: null,
    canvas: null,
    particles: [],
    mouseCoordinates: {x: 0, y: 0},
    config: {
        id: 'canvas',                   //
        count: 100,                     // 默认创建粒子数量
        radius: 1,                      // 默认粒子半径
        vxRange: [-1, 1],               // 默认粒子横向移动速度范围
        vyRange: [-1, 1],               // 默认粒子纵向移动速度范围
        scaleRange: [.5, 1],            // 默认粒子缩放比例范围
        lineLenThreshold: 125,          // 默认连线长度阈值
        color: 'rgba(255,255,255,.2)'   // 默认粒子、线条的颜色
    },
    init: function(newConfig) {

        // 更新config配置
        newConfig && Object.keys(newConfig).forEach(function(key) {
            _this.config[key] = newConfig[key];
        });

        var _this = this;
        this.canvas = document.getElementById(this.config.id);
        this.ctx = this.canvas.getContext('2d');

        // 只有在浏览器支持canvas的情况下才有效
        if(this.ctx) {

            Utils.updateWindowSize();
            var windowSize = Utils.getWindowSize();

            // 设置canvas宽高
            this.canvas.width = windowSize.width;
            this.canvas.height = windowSize.height;

            // 生成粒子
            var times = this.config.count;
            this.particles = [];
            while(times--) {
                this.particles.push(new Particle({
                    x: Utils.rangeRandom(this.config.radius, windowSize.width - this.config.radius),
                    y: Utils.rangeRandom(this.config.radius, windowSize.height - this.config.radius),
                    vx: Utils.rangeRandom(this.config.vxRange[0], this.config.vxRange[1]),
                    vy: Utils.rangeRandom(this.config.vyRange[0], this.config.vyRange[1]),
                    color: this.config.color,
                    scale: Utils.rangeRandom(this.config.scaleRange[0], this.config.scaleRange[1]),
                    radius: this.config.radius
                }));
            }

            // 监听鼠标的mouseMove事件,记录下鼠标的x,y坐标
            window.addEventListener('mousemove', this.handleMouseMove.bind(this), false);

            // 监听窗口大小改变事件
            window.addEventListener('resize', this.handleWindowResize.bind(this), false);

            // 兼容requestAnimationFrame
            Utils.supportRequestAnimationFrame();
            window.requestAnimationFrame(this.draw.bind(this));
        }
    },
    move: function() {

        var windowSize = Utils.getWindowSize();

        this.particles.forEach(function(item) {

            // 更新粒子坐标
            item.x += item.vx;
            item.y += item.vy;

            // 如果粒子碰到了左墙壁或右墙壁,则改变粒子的横向运动方向
            if((item.x - item.radius < 0) || (item.x + item.radius > windowSize.width)) {
                item.vx *= -1;
            }

            // 如果粒子碰到了上墙壁或下墙壁,则改变粒子的纵向运动方向
            if((item.y - item.radius < 0) || (item.y + item.radius > windowSize.height)) {
                item.vy *= -1;
            }
        });
    },
    draw: function() {

        var _this = this;
        var lineLenThreshold = this.config.lineLenThreshold;
        var windowSize = Utils.getWindowSize();

        // 每次重新绘制之前,需要先清空画布,把上一次的内容清空
        this.ctx.clearRect(0, 0, windowSize.width, windowSize.height);

        // 绘制粒子
        this.particles.forEach(function(item) {
            item.draw(_this.ctx);
        });

        // 绘制粒子之间的连线
        for(var i = 0; i < this.particles.length; i++) {
            for(var j = i + 1; j < this.particles.length; j++) {
                var distance = Math.sqrt(Math.pow(this.particles[i].x - this.particles[j].x, 2) + Math.pow(this.particles[i].y - this.particles[j].y, 2));
                if(distance < lineLenThreshold) {
                    // 这里我们让距离远的线透明度淡一点,距离近的线透明度深一点
                    this.ctx.strokeStyle = this.translateColors(this.config.color, (1 - distance / lineLenThreshold));
                    this.ctx.beginPath();
                    this.ctx.moveTo(this.particles[i].x, this.particles[i].y);
                    this.ctx.lineTo(this.particles[j].x, this.particles[j].y);
                    this.ctx.closePath();
                    this.ctx.stroke();
                }
            }
        }

        // 绘制粒子和鼠标之间的连线
        for(i = 0; i < this.particles.length; i++) {
            distance = Math.sqrt(Math.pow(this.particles[i].x - this.mouseCoordinates.x, 2) + Math.pow(this.particles[i].y - this.mouseCoordinates.y, 2));
            if(distance < lineLenThreshold) {
                this.ctx.strokeStyle = this.translateColors(this.config.color, (1 - distance / lineLenThreshold));
                this.ctx.beginPath();
                this.ctx.moveTo(this.particles[i].x, this.particles[i].y);
                this.ctx.lineTo(this.mouseCoordinates.x, this.mouseCoordinates.y);
                this.ctx.closePath();
                this.ctx.stroke();
            }
        }

        // 粒子移动,更新相应的x, y坐标
        this.move();

        // 循环调用draw方法
        window.requestAnimationFrame(this.draw.bind(this));
    },
    handleMouseMove: function(event) {

        var x, y;
        event = event || window.event;

        if(event.pageX || event.pageY) {
            x = event.pageX;
            y = event.pageY;
        } else {
            x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
            y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
        }

        this.mouseCoordinates = {x: x, y: y};
    },
    handleWindowResize: function() {
        Utils.updateWindowSize();
        var windowSize = Utils.getWindowSize();
        this.canvas.width = windowSize.width;
        this.canvas.height = windowSize.height;
    },
    translateColors: function(colorStr, ratio) {

        var r, g, b, a = 1, colorValues;

        if(colorStr[0] === '#') {                   // 传的是#RRGGBB形式
            r = parseInt(colorStr.slice(1, 3), 16);
            g = parseInt(colorStr.slice(3, 5), 16);
            b = parseInt(colorStr.slice(5, 7), 16);
        } else if(colorStr.startsWith('rgb(')) {     // 传的是rgb(r,g,b)形式
            colorStr = colorStr.slice(4, colorStr.length - 1);
            colorValues = colorStr.split(',');
            r = parseInt(colorValues[0].trim());
            g = parseInt(colorValues[1].trim());
            b = parseInt(colorValues[2].trim());
        } else if(colorStr.startsWith('rgba(')) {    // 传的是rgba(r,g,b,a)形式
            colorStr = colorStr.slice(5, colorStr.length - 1);
            colorValues = colorStr.split(',');
            r = parseInt(colorValues[0].trim());
            g = parseInt(colorValues[1].trim());
            b = parseInt(colorValues[2].trim());
            a = parseFloat(colorValues[3].trim());
        }

        return 'rgba(' + r + ',' + g + ',' + b + ',' + a * ratio + ')';
    },
    run: function(config) {
        this.init(config);
    }
};

/**
 * Particle 粒子类
 */
function Particle(attr) {

    // 粒子属性
    this.x = attr.x;            // 粒子在画布中的横坐标
    this.y = attr.y;            // 粒子在画布中的纵坐标
    this.vx = attr.vx;          // 粒子的横向运动速度
    this.vy = attr.vy;          // 粒子的纵向运动速度
    this.color = attr.color;    // 粒子的颜色
    this.scale = attr.scale;    // 粒子的缩放比例
    this.radius = attr.radius;  // 粒子的半径大小

    // 绘制方法
    if(typeof Particle.prototype.draw === 'undefined') {
        Particle.prototype.draw = function(ctx) {
            // canvas画圆方法
            ctx.beginPath();
            ctx.fillStyle = this.color;
            ctx.strokeStyle = this.color;
            ctx.arc(this.x, this.y, this.radius * this.scale, 0, 2 * Math.PI, false);
            ctx.closePath();
            ctx.fill();
        }
    }
}

// 工具
var Utils = {
    _windowSize: {
        width: 0,
        height: 0
    },
    getWindowSize: function() {
        return this._windowSize;
    },
    updateWindowSize: function() {
        this._windowSize.width = this.getWindowWidth();
        this._windowSize.height = this.getWindowHeight();
    },
    getWindowWidth: function() {
        return window.innerWidth || document.documentElement.clientWidth;
    },
    getWindowHeight: function() {
        return window.innerHeight || document.documentElement.clientHeight;
    },
    rangeRandom: function(min, max) {
        const diff = max - min;
        return min + Math.random() * diff;
    },
    supportRequestAnimationFrame: function() {
        if(!window.requestAnimationFrame) {
            window.requestAnimationFrame = (
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function (callback) {
                    setInterval(callback, 1000 / 60)
                }
            );
        }
    }
};

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值