这样就可以正常显示了:
例如:
context.font="40px Arial"; context.fillText("Hello world",200,200); context.strokeText("Hello world",200,300)效果如下:
好的开始是成功的一半
简单介绍了下 canvas 的常用 api,大家发现是不是也没有那么难呢~( ̄▽ ̄)~*,那么让我们回到标题,一起来看一下这个少女心满满的例子是怎样实现的~
canvas 其实写一个炫酷的特效在技术上并不难,难的是你的创意,因为 canvas 实现粒子的效果还是比较惊艳的,但其实代码都是比较简单的,无非就是随机的创建图形或者路径,当然图形也是闭合的路径。在加上一定的位移就可以了。但是你要设计出一个好的特效是非常不容易的。
所以我们就先来分析一下这个效果由那几部分构成,将其拆分开来。
特效pc端演示地址: sunshine940326.github.io/canvasStar/ (当然,可以直接查看我的博客,背景暂时就是这个,不知道什么时候会变,捂脸ing:cherryblog.site/)
分析 star 的表现和行为我们可以将其一直位移向上的粒子称为 star,我们观察 star 的特点:
所以我们就可以总结出 star 的特点就是总数固定,创建时坐标和半径还有透明度随机,匀速上升。是不是很简单了呢~[]~( ̄▽ ̄)~*
分析 dot 的表现和行为再让我们来看一下随着鼠标移入产生的粒子,我们称为 dot,同理,我们观察得到 dot 的特点
这样,我们就完成了一半了呢~将事件屡清楚之后我们就可以开始着手撸代码了!
背景的 HTML 和 CSS其实需要的 HTML 代码和 CSS 代码很简答的,HTML 只需要一行就可以了呢,设置一个渐变的背景蒙层和一个 canvas 标签。
<div class="filter"></div> <canvas id="canvas"></canvas>CSS 如下:
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: black; background: linear-gradient(to bottom, #dcdcdc 0%, palevioletred 100%); } #main-canvas { width: 100%; height: 100%; } .filter { width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: #fe5757; animation: colorChange 30s ease-in-out infinite; animation-fill-mode: both; mix-blend-mode: overlay; } @keyframes colorChange { 0%, 100% { opacity: 0; } 50% { opacity: .7; } }是的,我使用的是一个渐变的背景,不仅是从上到下的渐变,并且颜色也是会渐变的,效果如下:
这里我使用的是原型的方式,将 draw 、 cache 、 move 和 die 方法都设置在 Star 的原型上,这样在使用 new 创建对象的时候,每一个 star 都可以继承这些方法。
Star.prototype = { draw : function () { if (!this.useCacha) { ctx.save(); ctx.fillStyle = this.color; ctx.shadowBlur = this.r * 2; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); ctx.restore(); } else { ctx.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r); } }, cache : function () { this.cacheCtx.save(); this.cacheCtx.fillStyle = this.color; this.cacheCtx.shadowColor = "white"; this.cacheCtx.shadowBlur = this.r * 2; this.cacheCtx.beginPath(); this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI); this.cacheCtx.closePath(); this.cacheCtx.fill(); this.cacheCtx.restore(); }, move : function () { this.y -= move_distance; if (this.y <= -10) { this.y += HEIGHT + 10; } this.draw(); }, die : function () { stars[this.id] = null; delete stars[this.id] } }; 绘制 dot function Dot(id, x, y, useCache) { this.id = id; this.x = x; this.y = y; this.r = Math.floor(Math.random() * dot_r)+1; this.speed = dot_speeds; this.a = dot_alpha; this.aReduction = dot_aReduction; this.useCache = useCache; this.dotCanvas = document.createElement("canvas"); this.dotCtx = this.dotCanvas.getContext("2d"); this.dotCtx.width = 6 * this.r; this.dotCtx.height = 6 * this.r; this.dotCtx.a = 0.5; this.color = "rgba(255,255,255," + this.a +")"; this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")"; this.linkColor = "rgba(255,255,255," + this.a/4 + ")"; this.dir = Math.floor(Math.random()*140)+200; if( useCache){ this.cache() } } 让每一个 dot 动起来 Dot.prototype = { draw : function () { if( !this.useCache){ ctx.save(); ctx.fillStyle = this.color; ctx.shadowColor = "white"; ctx.shadowBlur = this.r * 2; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); ctx.restore(); }else{ ctx.drawImage(this.dotCanvas, this.x - this.r * 3, this.y - this.r *3); } }, cache : function () { this.dotCtx.save(); this.dotCtx.a -= this.aReduction; this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")"; this.dotCtx.fillStyle = this.dotCtx.color; this.dotCtx.shadowColor = "white"; this.dotCtx.shadowBlur = this.r * 2; this.dotCtx.beginPath(); this.dotCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI, false); this.dotCtx.closePath(); this.dotCtx.fill(); this.dotCtx.restore(); }, link : function () { if (this.id == 0) return; var previousDot1 = getPreviousDot(this.id, 1); var previousDot2 = getPreviousDot(this.id, 2); var previousDot3 = getPreviousDot(this.id, 3); var previousDot4 = getPreviousDot(this.id, 4); if (!previousDot1) return; ctx.strokeStyle = this.linkColor; ctx.moveTo(previousDot1.x, previousDot1.y); ctx.beginPath(); ctx.lineTo(this.x, this.y); if (previousDot2 != false) ctx.lineTo(previousDot2.x, previousDot2.y); if (previousDot3 != false) ctx.lineTo(previousDot3.x, previousDot3.y); if (previousDot4 != false) ctx.lineTo(previousDot4.x, previousDot4.y); ctx.stroke(); ctx.closePath(); }, move : function () { this.a -= this.aReduction; if(this.a <= 0 ){ this.die(); return } this.dotCtx.a -= this.aReduction; this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")"; this.color = "rgba(255,255,255," + this.a + ")"; this.linkColor = "rgba(255,255,255," + this.a/4 + ")"; this.x = this.x + Math.cos(degToRad(this.dir)) * this.speed; this.y = this.y + Math.sin(degToRad(this.dir)) * this.speed; this.draw(); this.link(); }, die : function () { dots[this.id] = null; delete dots[this.id]; } }; 鼠标移入事件监听此外,我们还需要设置一些其他的函数和对鼠标移入事件的监听,这里就不再赘述了,感兴趣的同学可以直接到 github 下载源码。
canvas 离屏渲染优化我所使用的离屏优化是基于此文,原文写的很好,大家感兴趣的话可以去看一下: …
因为这个效果之前我也在博客用当做背景过,不少同学都反应很卡,所以我就找了下优化的教程做了下优化,我发现对性能影响最大的可能就是 canvas 的离屏渲染优化了,这也是 canvas 的最常见优化之一。
名字听起来很复杂,什么离屏渲染,其实就是设置缓存,绘制图像的时候在屏幕之外的地方绘制好,然后再直接拿过来用,这不就是缓存的概念吗?!︿( ̄︶ ̄)︿.