canvas火焰动态效果 随鼠标移动

效果图

从上图可以看出,主要分为4个部分:

Fire单词字样

随鼠标而动的火光

呈现黄白红色的火焰

不断盘旋往上的火花

除了Fire字样,其他三样都随鼠标移动。

那么,就从如何实现这四样效果入手。

全局变量的声明

            //更新页面用requestAnimationFrame替代setTimeout
            window.requestAnimationFrame = window.requestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.msRequestAnimationFrame;

            var canvas = document.getElementById('fire');
            var ctx = canvas.getContext('2d');

            var w = canvas.width = canvas.offsetWidth;
            var h = canvas.height = canvas.offsetHeight;

1. 实现Fire单词字样,这里用到一个字体,css代码中引入

@import url(https://fonts.googleapis.com/css?family=Amatic+SC);
                    //Fire单词
                    ctx.font = "15em Amatic SC";
                    ctx.textAlign = "center";
                    ctx.strokeStyle = "rgb(50, 20, 0)";
                    ctx.fillStyle = "rgb(120, 10, 0)";
                    ctx.lineWidth = 2;
                    ctx.strokeText("Fire", w / 2, h * .72);
                    ctx.fillText("Fire", w / 2, h * .72);

这里Filre字样是填充效果,外面还包着一层未填充的Fire单词效果,颜色为 rgb(50, 20, 0)。

2. 实现随鼠标移动的火光。

                    //火光阴影
                    var grd = ctx.createRadialGradient(this.x, this.y - 200, 200,
                        this.x, this.y - 100, 0);
                    grd.addColorStop(0, "rgb(15, 5, 2)");
                    grd.addColorStop(1, "rgb(30, 10, 2)");

                    ctx.beginPath();
                    ctx.arc(this.x, this.y - 100, 200, 0, 2 * Math.PI);
                    ctx.fillStyle = grd;
                    ctx.fill();

这里用到了渐变色,原理可查看http://www.runoob.com/tags/canvas-createradialgradient.html 。

具体如何随鼠标移动,放到最后面讲。

3. 实现火焰,由图可以看出,就是很多不同颜色的圆,因为要画很多个圆,这里就定义一个对象,方便多次生成实例。

            function flame(x, y) {
                this.cx = x;
                this.cy = y;
                this.x = rand(this.cx - 25, this.cx + 25);
                this.y = rand(this.cy - 5, this.cy + 5);
                this.vx = rand(-1, 1);
                this.vy = rand(1, 3);
                this.r = rand(20, 30);
                this.life = rand(3, 6);
                this.alive = true;
                this.c = {
                    h: Math.floor(rand(2, 40)),
                    s: 100,
                    l: rand(80, 100),
                    a: 0,
                    ta: rand(0.8, 0.9)
                }

                this.update = update;

                function update() {
                    this.y -= this.vy;
                    this.vy += 0.05;

                    this.x += this.vx;

                    if (this.x < this.cx) {
                        this.vx += 0.1;
                    } else {
                        this.vx -= 0.1;
                    }

                    if (this.r > 0) {
                        this.r -= 0.1;
                    } else {
                        this.r = 0;
                    }

                    this.life -= 0.15;
                    if (this.life <= 0) {
                        this.c.a -= 0.05;
                        if (this.c.a <= 0) {
                            this.alive = false;
                        }
                    } else if (this.life > 0 && this.c.a < this.c.ta) {
                        this.c.a += .08;
                    }
                }

                this.draw = draw;

                function draw(ctx) {
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.r * 3, 0, 2 * Math.PI);
                    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a / 20) + ")";
                    ctx.fill();

                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
                    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")";
                    ctx.fill();
                }
            }

其中HSLA(H,S,L,A)为一种色彩模式 https://www.html.cn/book/css/values/color/hsla.htm,为了实现火焰效果,这些圆的颜色各不相同,色调亮度在一定范围内随机生成。而从下面代码中可以看出

            this.life -= 0.15;
                    if (this.life <= 0) {
                        this.c.a -= 0.05;
                        if (this.c.a <= 0) {
                            this.alive = false;
                        }
                    } else if (this.life > 0 && this.c.a < this.c.ta) {
                        this.c.a += .08;
                    }

透明度a值是先不断增加,再不断减小直到a <= 0 火焰alive 为false,这个火焰就可以退休了,会从火焰数组中删除这个实例。

4. 火花,可以看出火花是由很多线条组成的,这里也要定义一个对象

            function spark(x, y) {
                this.cx = x;
                this.cy = y;
                this.x = rand(this.cx - 40, this.cx + 40);
                this.y = rand(this.cy, this.cy + 5);
                this.lx = this.x;
                this.ly = this.y;
                this.vx = rand(-4, 4);
                this.vy = rand(1, 3);
                this.r = rand(0, 1);
                this.life = rand(4, 5);
                this.alive = true;
                this.c = {
                    h: Math.floor(rand(2, 40)),
                    s: 100,
                    l: rand(40, 100),
                    a: rand(0.8, 0.9)
                }

                this.update = update;

                function update() {
                    this.lx = this.x;
                    this.ly = this.y;

                    this.y -= this.vy;
                    this.x += this.vx;

                    if (this.x < this.cx) {
                        this.vx += 0.2;
                    } else {
                        this.vx -= 0.2;
                    }

                    this.vy += 0.08;

                    this.life -= 0.1;
                    if (this.life <= 0) {
                        this.c.a -= 0.05;
                        if (this.c.a <= 0) {
                            this.alive = false;
                        }
                    }
                }

                this.draw = draw;

                function draw(ctx) {
                    ctx.beginPath();
                    ctx.moveTo(this.lx, this.ly);
                    ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a / 2) + ")";
                    ctx.lineWidth = this.r * 2;
                    ctx.lineCap = 'round';
                    ctx.stroke();

                    ctx.beginPath();
                    ctx.moveTo(this.lx, this.ly);
                    ctx.lineTo(this.x, this.y);
                    ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")";
                    ctx.lineWidth = this.r;
                    ctx.stroke();
                }
            }

最后,如何实现随鼠标移动的效果。

定义一个Fire对象,在对象方法中实现上述4中元素的绘制与更新显示

            //定义fire对象
            function fire(x, y) {
                this.x = x;
                this.y = y;

                this.aFires = [];
                this.aSparks = [];
                this.aSparks2 = [];
                
                this.update = update;
                function update(x, y) {
                    this.aFires.push(new flame(x, y));
                    this.aSparks.push(new spark(x, y));
                    this.aSparks2.push(new spark(x, y));
                    for (var i = 0; i < this.aFires.length; i++) {
                        if (this.aFires[i].alive) {
                            this.aFires[i].update();
                        } else {
                            this.aFires.splice(i, 1);
                        }
                    }
                    
                    //console.log(aFires.length)
                    for (var i = 0; i < this.aSparks.length; i++) {
                        if (this.aSparks[i].alive) {
                            this.aSparks[i].update();
                        } else {
                            this.aSparks.splice(i, 1);
                        }
                    }
                    
                    for (var i = 0; i < this.aSparks2.length; i++) {
                        if (this.aSparks2[i].alive) {
                            this.aSparks2[i].update();
                        } else {
                            this.aSparks2.splice(i, 1);
                        }
                    }
                }
                
                this.draw = draw;

                function draw(ctx) {
                    //设置或返回如何将一个源(新的)图像绘制到目标(已有的)的图像上。
                    //source-over    默认。在目标图像上显示源图像。
                    //源图像 = 您打算放置到画布上的绘图。
                    //目标图像 = 您已经放置在画布上的绘图。
                    ctx.globalCompositeOperation = "source-over";
                    ctx.fillStyle = "rgba(15, 5, 2, 1)";
                    ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);

                    //火光阴影
                    var grd = ctx.createRadialGradient(this.x, this.y - 200, 200,
                        this.x, this.y - 100, 0);
                    grd.addColorStop(0, "rgb(15, 5, 2)");
                    grd.addColorStop(1, "rgb(30, 10, 2)");

                    ctx.beginPath();
                    ctx.arc(this.x, this.y - 100, 200, 0, 2 * Math.PI);
                    ctx.fillStyle = grd;
                    ctx.fill();

                    //Fire单词
                    ctx.font = "15em Amatic SC";
                    ctx.textAlign = "center";
                    ctx.strokeStyle = "rgb(50, 20, 0)";
                    ctx.fillStyle = "rgb(120, 10, 0)";
                    ctx.lineWidth = 2;
                    ctx.strokeText("Fire", w / 2, h * .72);
                    ctx.fillText("Fire", w / 2, h * .72);

                    ctx.globalCompositeOperation = "overlay";
                    for (var i = 0; i < this.aFires.length; i++) {
                        this.aFires[i].draw(ctx);
                    }

                    ctx.globalCompositeOperation = "soft-light";
                    for (var i = 0; i < this.aSparks.length; i++) {
                        if ((i % 2) === 0)
                            this.aSparks[i].draw(ctx);
                    }

                    ctx.globalCompositeOperation = "color-dodge";
                    for (var i = 0; i < this.aSparks2.length; i++) {
                        this.aSparks2[i].draw(ctx);
                    }
                }
            }

可以看到在draw方法中,实现了绘制4个元素。

在update方法中就是生成火焰和火花的实例,以及显示状态的变化。

最后实现一个fire的实例,引入fire的draw和update方法,就可以实现火焰效果了。

            var current_fire = new fire(w * .5, h * .75);
            function init() {
                current_fire.update(current_fire.x, current_fire.y);
                if (current_fire.x) {
                    current_fire.draw(ctx);
                } 
                requestAnimationFrame(init);
            }

而随鼠标移动就是要动态改变current_fire的x, y坐标。

            window.onmousemove = function(e) {
                e = e || window.event;
                current_fire.x = e.clientX;
                current_fire.y = e.clientY;
            }

            window.onmouseout = function() {
                current_fire.x = w * .5;
                current_fire.y = h * .75;
            }

最后,源码下载地址https://github.com/sakurayj/canvas/tree/master/test 。

猜你喜欢

转载自www.cnblogs.com/sakurayj/p/10318957.html