效果图
从上图可以看出,主要分为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 。