本文首发于我的个人博客:http://cherryblog.site/
github项目地址:https://github.com/sunshine940326/canvasStar
项目演示地址:https://sunshine940326.github.io/canvasStar/
[toc]
之前看到了一个很好看的canvas效果,然后拿来做我的博客背景,不少童鞋留言说求教程,并且反应说太耗内存,于是前一段我就重写了一遍,并且使用离屏渲染进行优化,效果还是挺显著的。但是因为毕竟是canvas,需要一直进行重绘,所以还是比较耗内存的,但是比优化之前已经好很多了。并且最近准备自己写插件,于是就拿这个练手了,
github地址:https://github.com/sunshine940326/canvasStar
代码还有很多的不足,求大神 review (づ。◕‿‿◕。)づ~
canvas 基本知识
什么是 canvas
canvas
是 HTML5 新定义的标签,通过使用脚本(通常是 JavaScript)绘制图形。<canvas>
标签只是图形容器,相当于一个画布,canvas
元素本身是没有绘图能力的。所有的绘制工作必须在 JavaScript 内部完成,相当于使用画笔在画布上画画。
默认情况下,<canvas>
没有边框和内容。默认是一个 300150 的画布,所以我们创建了 <canvas>
之后要对其设置宽高。
**我们可以通过html属性‘width’,‘height’来设置canvas的宽高,不可以通过 css 属性来设置宽高。因为通过 css 属性设置的宽高会使 canvas 内的图像按照 300150 时的比例放大或缩小**
getContext()
context
是一个封装了很多绘图功能的对象,我们在页面中创建一个 canvas
标签之后,首先要使用 getContext()
获取 canvas 的上下文环境,目前 getContext()
的参数只有 2d
,暂时还不支持 3d
getContext("2d")
对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
canvas 元素绘制图像
canvas 创建图形有两种方式
context.fill()
fill()
方法填充当前的图像(路径)。默认颜色是黑色。在填充前要先使用 fillStyle
设置填充的颜色或者渐变,并且如果路径未关闭,那么 fill()
方法会从路径结束点到开始点之间添加一条线,以关闭该路径(正如 closePath()
一样),然后填充该路径。
context.stroke()
stroke()
方法会实际地绘制出通过 moveTo()
和 lineTo()
方法定义的路径。默认颜色是黑色。在进行图形绘制前,要设置好绘图的样式
|
|
绘制矩形
用 canvas 绘制一个矩形很简单
|
|
- x :起始点的 x 坐标
- y :起始点的 y 坐标
- width : 矩形的宽
- height : 矩形的高
|
|
我们可以看出,在没有设置颜色的情况下,默认是黑色的。
我们还可以通过设置 fillStyle
或者 fillStyle
改变其填充颜色。
|
|
清除矩形区域
|
|
var canvas = document.getElementById(‘canvas’);
var context = canvas.getContext(“2d”);
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);
context.fillStyle = “pink”;
context.strokeStyle = “darkred”;
context.fillRect(0, 120, 100, 100);
context.strokeRect(120, 120, 100, 100);
context.clearRect( 50,50,120,120)
|
|
context.beginPath();
context.arc(300, 350, 100, 0, Math.PI * 2, true);
//不关闭路径路径会一直保留下去
context.closePath();
context.fillStyle = ‘rgba(0,255,0,0.25)’;
context.fill();
|
|
context.beginPath();
context.arc(600, 350, 100, 0, Math.PI , true);
context.strokeStyle = 'pink';
context.closePath();
context.stroke();
context.beginPath();
context.arc(300, 350, 100, 0, Math.PI , true);
context.strokeStyle = 'red';
//没有closePath
context.stroke();
|
|
context.strokeStyle = 'pink';
context.moveTo(0, 0);
context.lineTo(100, 100);
context.stroke();*/
|
|
context.strokeStyle = 'pink';
context.lineTo(100, 100);
context.lineTo(200, 200);
context.stroke();*/
|
|
// 绘制片段
context.strokeStyle = ‘pink’;
context.lineTo(200, 200);
context.lineTo(200, 100);
context.lineTo(100,50);
context.stroke();
|
|
var n = 0;
var dx = 150;
var dy = 150;
var s = 100;
context.beginPath();
context.fillStyle = ‘pink’;
context.strokeStyle = ‘rgb(0,0,100)’;
var x = Math.sin(0);
var y = Math.cos(0);
var dig = Math.PI / 15 5;
for (var i = 0; i < 6; i++) {
var x = Math.sin(i dig);
var y = Math.cos(i dig);
context.lineTo(dx + x s, dy + y * s);
console.log( x ,y )
}
context.closePath();
context.fill();
context.stroke();
|
|
var n = 0;
var dx = 150;
var dy = 150;
var s = 100;
context.beginPath();
context.fillStyle = ‘pink’;
context.strokeStyle = ‘rgb(0,0,100)’;
var x = Math.sin(0);
var y = Math.cos(0);
var dig = Math.PI / 15 7;
for (var i = 0; i < 30; i++) {
var x = Math.sin(i dig);
var y = Math.cos(i dig);
context.lineTo(dx + x s, dy + y * s);
console.log( x ,y )
}
context.closePath();
context.fill();
context.stroke();
|
|
var g1 = context.createLinearGradient(0, 0, 0, 300);
g1.addColorStop(0, '#E55D87');
g1.addColorStop(1, '#5FC3E4');
context.fillStyle = g1;
context.fillRect(0, 0, 400, 300);
|
|
// 同心圆径向渐变
var g1 = context.createRadialGradient(200, 150, 0, 200, 150, 200);
g1.addColorStop(0.1, ‘#F09819’);
g1.addColorStop(1, ‘#EDDE5D’);
context.fillStyle = g1;
context.beginPath();
context.arc(200, 150, 100, 0, Math.PI * 2, true);
context.closePath();
context.fill();
|
|
//不同圆心的径向渐变模型
var g1 = context.createRadialGradient(100, 150, 10, 300, 150, 80);
g1.addColorStop(0.1, ‘#F09819’);
g1.addColorStop(0.8, ‘red’);
g1.addColorStop(1, ‘#EDDE5D’);
context.fillStyle = g1;
context.fillRect(0, 0, 300, 500);
|
|
shadowOffsetX:设置或返回阴影距形状的水平距离(默认值为 0)
shadowOffsetY:设置或返回阴影距形状的垂直距离(默认值为 0)
shadowColor:设置或返回用于阴影的颜色
shadowBlur:设置或返回用于阴影的模糊级别(值越大越模糊)
|
|
context.fillStyle = 'white';
context.beginPath();
context.arc(100,100,10,0,2 * Math.PI);
context.shadowColor = 'white';
context.shadowBlur = 10;
context.fill();
context.closePath();
|
|
// 设置剪切区域(粉色矩形)
context.rect(0,0,500,400);
context.fillStyle = "pink";
context.fill();
context.clip();
// 在剪切区域中绘制图形(白色矩形)
context.fillStyle = "white";
context.fillRect(10,10,100,100);
// 之后绘制的图形只能显示在剪切区域之内(红色矩形)
context.fillStyle = "red";
context.fillRect(100,100,600,600)
|
|
context.save();
// 设置剪切区域
context.rect(0,0,500,400);
context.fillStyle = “pink”;
context.fill();
context.clip();
// 在剪切区域中绘制图形
context.fillStyle = "white";
context.fillRect(10,10,100,100);
context.restore();
// 之后绘制的图形只能显示在剪切区域之内
context.fillStyle = "red";
context.fillRect(100,100,600,600)
|
|
fillText(text,x,y):绘制实心文字
strokeText():绘制文字描边(空心)
textAlign:设置或返回文本内容的当前对齐方式
textBaseline:设置或返回在绘制文本时使用的当前文本基线
font:设置或返回文本内容的当前字体属性
|
|
context.font="40px Arial";
context.fillText("Hello world",200,200);
context.strokeText("Hello world",200,300)
|
|
|
|
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;
}
}
|
|
/*
* @var star_r:star半径系数,系数越大,半径越大
* @var star_alpha:生成star的透明度,star_alpha越大,透明度越低
* @var initStarsPopulation:初始化stars的个数
* @var move_distance:star位移的距离,数值越大,位移越大
* @var dot_r : dot半径系数,系数越大,半径越大
* @var dot_speeds : dots运动的速度
* @var dot_alpha : dots的透明度
* @var aReduction:dot消失条件,透明度小于aReduction时消失
* @var dotsMinDist:dot最小距离
* @var maxDistFromCursor:dot最大距离
* */
var config = {
star_r : 3,
star_alpha : 5,
initStarsPopulation : 150,
move_distance : 0.25,
dot_r : 5,
dot_speeds : 0.5,
dot_alpha : 0.5,
dot_aReduction : 0.01,
dotsMinDist : 5,
maxDistFromCursor : 50,
};
var stars = [],
dots = [],
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
WIDTH,
HEIGHT,
mouseMoving = false,
mouseMoveChecker,
mouseX,
mouseY;
|
|
/* 设置单个 star
* @param id:id
* @param x:x坐标
* @param y:y坐标
* @param useCache:是否使用缓存
* */
function Star(id, x, y) {
this.id = id;
this.x = x;
this.y = y;
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.r = Math.floor(Math.random() * star_r) + 1;
this.cacheCtx.width = 6 * this.r;
this.cacheCtx.height = 6 * this.r;
var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
this.color = "rgba(255,255,255," + alpha + ")";
if (useCache) {
this.cache()
}
}
|
|
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]
}
};
|
|
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.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];
}
};
|
|
/* 设置单个star
* @param id:id
* @param x:x坐标
* @param y:y坐标
* @param useCache:是否使用缓存
* */
function Star(id, x, y, useCache) {
this.id = id;
this.x = x;
this.y = y;
this.useCacha = useCache;
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.r = Math.floor(Math.random() * star_r) + 1;
this.cacheCtx.width = 6 * this.r;
this.cacheCtx.height = 6 * this.r;
var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
this.color = "rgba(255,255,255," + alpha + ")";
if (useCache) {
this.cache()
}
}
|
|
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
|
|
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();
},
`` 之后我们需要将我们绘制的离屏 canvas 使用
drawImage` 方法插入到我们最先开始创建的 canvas 画布中。
这里要注意的是,创建的离屏 canvas 的大小,因为太大的话同样会浪费性能,所以我们可以创建和我们每一个 star 粒子相同的 canvas ,但是这个例子中不适用,要将离屏的 canvas 设置的稍微大一些,因为我们还需要设置发光的效果(也就是设置阴影)。
发福利
发福利的时间到了~╰( ̄▽ ̄)╭,很多小伙伴对 canvas 不是很感兴趣,但是想直接使用这个效果,于是我就将其封装起来,你只需要引入这个 JS,在 HTML 中添加一个 id 为 canvas 的标签,然后设置相应的 CSS 就可以~
github 下载地址:https://github.com/sunshine940326/canvasStar
在 README 中有使用方法~因为是第一次自己封装函数,自己一个人在不停的摸索中前进,所以还有很多的不足,希望有大神可以指点一二~