canvas烟花效果-学习自用

<template>
    <div class="fire-view">
        <canvas id="fireworks"></canvas>
    </div>
</template>

<script>
export default {
    mounted() {
        this.init()
        // this.drawLine()
    },
    methods: {
        // 模拟向上发射的过程(这个是测试看效果用)
        drawLine() {
            // 基础信息
            var W = window.innerWidth,
                H = window.innerHeight,
                canvas = document.getElementById("fireworks"),
                context = canvas.getContext("2d");

                canvas.width = W;
                canvas.height = H;

            // 保证canvas宽高和屏幕一致
            if (W != window.innerWidth) {
                canvas.width = W = window.innerWidth;
            }
            if (H != window.innerHeight) {
                canvas.height = H = window.innerHeight;
            }
            
            var rb = new Rocket(200, H);

            // 1秒刷新60次 动画帧率(屏幕刷新率)保持在60次/秒保障动画效果的流畅性
            setInterval(loop, 1000 / 60);

            function loop(){
                context.fillStyle = "rgba(0, 0, 0, 0.05)";
                context.fillRect(0, 0, W, H);
                if(!rb.end){
                    rb.update();
                }else{
                    // x: [0, W]随机数 y: 高度
                    rb = new Rocket(Math.random() * W, H);
                }
                
            }
            function Rocket(x, y){
                // x: 燃放为止, y: 烟花高度
                this.pos = {
                    x: x,
                    y: y
                };
                this.size = 8; // 半径(单个的,不是指整个烟花半径)
                this.step = 1.9; // 速度
                this.end = false; // 停止向上
                
                this.update = function(){
                    if(this.end)return;
                    this.pos.x += this.step * 0.0; // 改变x的位置,模拟烟花倾斜度
                    this.pos.y -= this.step; // TODO
                    if(this.pos.y < 0 || this.pos.x > W)this.end = true;
                    this.render();
                };
                
                this.render = function(){
                    if(this.end)return;
                    var c = context;
                    c.save(); // 保存canvas的状态
                    // 绘图方式globalCompositeOperation
                    // Canvas的 globalCompositeOperation 属性决定下一次绘图时如何将一个源(新的)图像绘制到目标(已有)的图像上。此处设置为 \'lighter\' 使同时显示源图像和目标图像,否则会影响到循环中的画布重绘。
                    c.globalCompositeOperation = 'lighter';
                    
                    var x = this.pos.x, y = this.pos.y, r = this.size;
                    // 颜色径向渐变: createRadialGradient(渐变的开始圆的x坐标, 渐变的开始圆的y坐标, 开始圆的半径, 渐变的结束圆的x坐标, 渐变的结束圆的y坐标, 结束圆的半径)
                    // 其中 createRadialGradient() 函数的第1、2个参数与第4、5个参数分别表示渐变开始和渐变结束的圆心坐标。在这里设置为相同的值,使颜色过渡效果更自然真实。
                    var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
                    // addColorStop(介于0.0与1.0之间的值表示渐变中开始与结束之间的位置, color在结束位置显示的颜色值)
                    gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + 1 + ")");
                    gradient.addColorStop(1, "rgba(0, 0, 0, " + 1 + ")");
                    
                    c.fillStyle = gradient;
                    //beginPath 开始一段新的路径
                    c.beginPath();
                    c.arc(x, y, r, 0, Math.PI * 2, true);
                    // closePath()方法与之前有没有beginPath()无关,如果之前画出了一些路径,closePath()将最近画出的一条路径闭合
                    c.closePath();
                    c.fill();
                    
                    c.restore(); // 恢复画布
                };

            }
        },
        // 参考 https://codesandbox.io/embed/canvas-fireworks-65923?fontsize=14&hidenavigation=1&theme=dark
        init() {
            // 获取屏幕宽高
            var SCREEN_WIDTH = window.innerWidth,
            SCREEN_HEIGHT = window.innerHeight,
            mousePos = { // 烟花初始为止
                x: 400,
                y: 300
            },
            // 初始化canvas
            canvas = document.getElementById("fireworks"),
            context = canvas.getContext("2d"),
            particles = [],
            rockets = [],
            MAX_PARTICLES = 400,
            colorCode = 0;

            // init
            window.onload = function () {
                canvas.width = SCREEN_WIDTH;
                canvas.height = SCREEN_HEIGHT;
                setInterval(launch, 800);
                // 1秒刷新60次 动画帧率(屏幕刷新率)保持在60次/秒保障动画效果的流畅性
                setInterval(loop, 1000 / 60);
            };
            // 监听鼠标移动
            window.document.onmousemove = function (e) {
                e.preventDefault();
                mousePos = {
                    x: e.clientX,
                    y: e.clientY
                };
            };

            // 鼠标点击处发射烟花
            window.document.onmousedown = function (e) {
                for (var i = 0; i < 5; i++) {
                    launchFrom((Math.random() * SCREEN_WIDTH * 2) / 3 + SCREEN_WIDTH / 6);
                }
            };
            // 循环创建
            function launch() {
                launchFrom(mousePos.x);
            }
            // 创建多个烟花
            function launchFrom(x) {
                if (rockets.length < 10) {
                    var rocket = new Rocket(x);
                    rocket.explosionColor = Math.floor((Math.random() * 360) / 10) * 10;
                    rocket.vel.y = Math.random() * -3 - 4;
                    rocket.vel.x = Math.random() * 6 - 3;
                    rocket.size = 8;
                    rocket.shrink = 0.999;
                    rocket.gravity = 0.01;
                    rockets.push(rocket);
                }
            }

            function loop() {
                // 保证canvas宽高和屏幕一致 更新屏幕大小
                if (SCREEN_WIDTH !== window.innerWidth) {
                    canvas.width = SCREEN_WIDTH = window.innerWidth;
                }
                if (SCREEN_HEIGHT !== window.innerHeight) {
                    canvas.height = SCREEN_HEIGHT = window.innerHeight;
                }

                // 清除canvas
                context.fillStyle = "rgba(0, 0, 0, 0.05)";
                context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

                var existingRockets = [];

                for (var i = 0; i < rockets.length; i++) {
                    // update and render
                    rockets[i].update();
                    rockets[i].render(context);

                    // calculate distance with Pythagoras
                    var distance = Math.sqrt(
                    Math.pow(mousePos.x - rockets[i].pos.x, 2) +
                        Math.pow(mousePos.y - rockets[i].pos.y, 2)
                    );

                    // random chance of 1% if rockets is above the middle
                    var randomChance =
                    rockets[i].pos.y < (SCREEN_HEIGHT * 2) / 3
                        ? Math.random() * 100 <= 1
                        : false;

                    // 爆炸规则
                    // -屏幕的80%
                    // -正在下降
                    // -靠近鼠标
                    // -随机爆炸的概率为1%
                    if (
                        rockets[i].pos.y < SCREEN_HEIGHT / 5 ||
                        rockets[i].vel.y >= 0 ||
                        distance < 50 ||
                        randomChance
                    ) {
                        rockets[i].explode();
                    } else {
                        existingRockets.push(rockets[i]);
                    }
                }

                rockets = existingRockets;

                var existingParticles = [];

                for (var i = 0; i < particles.length; i++) {
                    particles[i].update();

                    // 渲染和保存可以渲染的粒子
                    if (particles[i].exists()) {
                    particles[i].render(context);
                    existingParticles.push(particles[i]);
                    }
                }

                // 使用现有粒子更新阵列-旧粒子应被垃圾收集
                particles = existingParticles;

                while (particles.length > MAX_PARTICLES) {
                    particles.shift(); // 删掉第一个
                }
            }
            
            // 爆炸的粒子效果
            function Particle(pos) {
                this.pos = {
                    x: pos ? pos.x : 0,
                    y: pos ? pos.y : 0
                };
                this.vel = {
                    x: 0,
                    y: 0
                };
                this.shrink = 0.97;
                this.size = 2;

                this.resistance = 1;
                this.gravity = 0;

                this.flick = false;

                this.alpha = 1; // 透明度
                this.fade = 0; // 透明度褪到几
                this.color = 0;
            }

            Particle.prototype.update = function () {
                // 施加阻力
                this.vel.x *= this.resistance;
                this.vel.y *= this.resistance;

                // 重力下降
                this.vel.y += this.gravity;

                // 基于速度更新位置
                this.pos.x += this.vel.x;
                this.pos.y += this.vel.y;

                // 收缩
                this.size *= this.shrink;

                // 淡出
                this.alpha -= this.fade;
            };
            
            Particle.prototype.render = function (c) {
                if (!this.exists()) {
                    return;
                }

                c.save();

                c.globalCompositeOperation = "lighter";

                var x = this.pos.x,
                    y = this.pos.y,
                    r = this.size / 2;
                // 颜色径向渐变: createRadialGradient(渐变的开始圆的x坐标, 渐变的开始圆的y坐标, 开始圆的半径, 渐变的结束圆的x坐标, 渐变的结束圆的y坐标, 结束圆的半径)
                // 其中 createRadialGradient() 函数的第1、2个参数与第4、5个参数分别表示渐变开始和渐变结束的圆心坐标。在这里设置为相同的值,使颜色过渡效果更自然真实。
                var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
                // 烟花颜色渐变
                // addColorStop(介于0.0与1.0之间的值表示渐变中开始与结束之间的位置, color在结束位置显示的颜色值)
                gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
                gradient.addColorStop(
                    0.8,
                    "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")"
                );
                gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");

                c.fillStyle = gradient;

                //beginPath 开始一段新的路径
                c.beginPath();
                c.arc(
                    this.pos.x,
                    this.pos.y,
                    this.flick ? Math.random() * this.size : this.size,
                    0,
                    Math.PI * 2,
                    true
                );

                c.closePath();
                c.fill();

                c.restore(); // 恢复画布
            };
            // 判断是否存在
            Particle.prototype.exists = function () {
                return this.alpha >= 0.1 && this.size >= 1;
            };

            // 向上发射的效果
            function Rocket(x) {
                Particle.apply(this, [ { x: x, y: SCREEN_HEIGHT } ]);

                this.explosionColor = 0;
            }
            Rocket.prototype = new Particle();
            Rocket.prototype.constructor = Rocket;
            // 爆炸
            Rocket.prototype.explode = function () {
                var count = Math.random() * 10 + 80;

                for (var i = 0; i < count; i++) {
                    var particle = new Particle(this.pos);
                    var angle = Math.random() * Math.PI * 2;

                    // emulate 3D effect by using cosine and put more particles in the middle
                    var speed = Math.cos((Math.random() * Math.PI) / 2) * 15;

                    particle.vel.x = Math.cos(angle) * speed;
                    particle.vel.y = Math.sin(angle) * speed;

                    particle.size = 10;

                    particle.gravity = 0.2;
                    particle.resistance = 0.92;
                    particle.shrink = Math.random() * 0.05 + 0.93;

                    particle.flick = true;
                    particle.color = this.explosionColor;

                    particles.push(particle);
                }
            };
            // 渲染
            Rocket.prototype.render = function (c) {
                if (!this.exists()) {
                    return;
                }

                c.save();

                c.globalCompositeOperation = "lighter";

                var x = this.pos.x,
                    y = this.pos.y,
                    r = this.size / 2;

                var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
                gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
                gradient.addColorStop(1, "rgba(0, 0, 0, " + this.alpha + ")");

                c.fillStyle = gradient;

                c.beginPath();
                c.arc(
                    this.pos.x,
                    this.pos.y,
                    this.flick ? (Math.random() * this.size) / 2 + this.size / 2 : this.size,
                    0,
                    Math.PI * 2,
                    true
                );
                c.closePath();
                c.fill();

                c.restore();
            };

        }
    }
}
</script>

<style lang="less" scoped>
.fire-view {
    background: #000;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    canvas {
        position: absolute;
        z-index: 0;
    }
}
</style>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值