# canvas使鼠标和屏幕互动
# onmousemove 事件
可以看出来,移动的轨迹是由一个一个的圆构成的,如果移动的速度过快的话,那么就可以明显看出一个一个的圆。
既然轨迹是由很多圆构成,那么就应该使用数组储存圆的信息(坐标、半径),然后在鼠标移动的时候将鼠标的位置信息储存在数组中。
所以在鼠标移动的过程先要获得鼠标的坐标,然后将鼠标的坐标以及其他信息 push 到数组中去:
window.onmousemove = function (event) {
mouseX = event.clientX;
mouseY = event.clientY;
round_arr.push({
mouseX: mouseX,
mouseY: mouseY,
r: para.r, // 设置半径每次增大的数值
o: 1, // 判断圆消失的条件,数值越大,消失得越快
})
};
# 设置 color
将圆的相关信息储存在 round_arr 数组中,现在要在 animate() 函数中将圆显示出来。
创建圆需要的坐标信息以及半径,在鼠标移动的事件中都已经将其 push 到 round_arr 数组中了,还有一个条件是需要设置的,那就是颜色。
怎么对颜色进行处理呢?在 para 参数中,可以看出,其中有设置 color 值。如果 color 值不为 false,那么设置的圆的颜色就是设置的 color 值;如果设置的 color 值为 false,那么圆的颜色就是随机的。
if (para.color) {
color2 = para.color;
} else {
color = Math.random() * 360;
}
那么怎么设置颜色的渐变呢?将 color 的颜色值依次增加一个增量。
if (!para.color) {
color += .1;
color2 = 'hsl(' + color + ',100%,80%)';
}
要让颜色一直改变,要将上面颜色改变的代码放在一个一直执行的函数。将上面改变颜色的代码放在 animate() 函数中。
# animate() 函数
需要一个一直在执行的函数,这个函数主要负责动画的 animate() 函数。从函数名就可以看出这个函数的作用,需要在该函数中写动画。
先来清除屏幕。
接着使用 round_arr 数组中的数据将一个一个的圆绘制出来。
for (var i = 0; i < round_arr.length; i++) {
ctx.fillStyle = color2;
ctx.beginPath();
ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
ctx.closePath();
ctx.fill();
round_arr[i].r += para.r;
round_arr[i].o -= para.o;
if( round_arr[i].o <= 0){
round_arr.splice(i,1);
i--;
}
}
还需要一直执行这个函数:
window.requestAnimationFrame(animate);
function animate() {
if (!para.color) {
color += .1;
color2 = 'hsl(' + color + ',100%,80%)';
}
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i < round_arr.length; i++) {
ctx.fillStyle = color2;
ctx.beginPath();
ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
ctx.closePath();
ctx.fill();
round_arr[i].r += para.r;
round_arr[i].o -= para.o;
if( round_arr[i].o <= 0){
round_arr.splice(i,1);
i--;
}
}
window.requestAnimationFrame(animate);
};
# 小结
- 创建 Canvas 元素,设置参数
- 鼠标移动事件,将坐标信息 push 到数组
- 设置颜色
- 设置动画
animate()函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
padding: 0;
margin: 0;
}
#canvas {
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
WIDTH = canvas.width = document.documentElement.clientWidth,
HEIGHT = canvas.height = document.documentElement.clientHeight,
para = {
num: 100,
color: false, // 颜色 如果是false 则是随机渐变颜色
r: 0.9,
o: 0.09, // 判断圆消失的条件,数值越大,消失的越快
a: 1,
},
color,
color2,
round_arr = [];
window.onmousemove = function (event) {
mouseX = event.clientX;
mouseY = event.clientY;
round_arr.push({
mouseX: mouseX,
mouseY: mouseY,
r: para.r,
o: 1
})
};
// 判断参数中是否设置了 color,如果设置了 color,就使用该值、
// 如果参数中的 color 为 false,那么就使用随机的颜色
if (para.color) {
color2 = para.color;
} else {
color = Math.random() * 360;
}
function animate() {
if (!para.color) {
color += .1;
color2 = 'hsl(' + color + ',100%,80%)';
}
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i < round_arr.length; i++) {
ctx.fillStyle = color2;
ctx.beginPath();
ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY,round_arr[i].r,0, Math.PI * 2);
ctx.closePath();
ctx.fill();
round_arr[i].r += para.r;
round_arr[i].o -= para.o;
if( round_arr[i].o <= 0){
round_arr.splice(i,1);
i--;
}
}
window.requestAnimationFrame(animate);
};
animate();
</script>
</body>
</html>
# 常见的 Canvas 优化方法
# 避免浮点数的坐标点
绘制图形时,长度与坐标应选取整数而不是浮点数,原因在于 Canvas 支持半个像素绘制。
会根据小数位实现插值算法实现绘制图像的反锯齿效果,如果没有必要请不要选择浮点数值。
# 使用多层画布去画一个复杂的场景
一般在游戏中这个优化方式会经常使用,但是在我们的背景特效中不经常使用,这个优化方式是将经常移动的元素和不经常移动的元素分层,避免不必要的重绘。
比如在游戏中,背景不经常变换和人物这些经常变换的元素分成不同的层,这样需要重绘的资源就会少很多。
# 用 CSS transform 特性缩放画布
如果你使用 left、top 这些 CSS 属性来写动画的话,那么会触发整个像素渲染流程 —— paint、layout 和 composition。
但是使用 transform 中的 translateX/Y 来切换动画,你将会发现,这并不会触发 paint 和 layout,仅仅会触发 composition 的阶段。
这是因为 transform 调用的是 GPU 而不是 CPU。
# 离屏渲染
名字听起来很复杂,什么离屏渲染,其实就是设置缓存,绘制图像的时候在屏幕之外的地方绘制好,然后再直接拿过来用,这不就是缓存的概念吗?!︿( ̄︶ ̄)︿.
建立两个 Canvas 标签,大小一致,一个正常显示,一个隐藏(缓存用的,不插入 DOM 中)。先将结果 draw 到缓存用的 canvas 上下文中,因为游离 Canvas 不会造成 UI 的渲染,所以它不会展现出来;再把缓存的内容整个裁剪再 draw 到正常显示用的 Canvas 上,这样能优化不少。
我们首先要在全局设置一个变量 useCache 来存放我们是否使用离屏渲染这种优化方式。
var useCache = true;
# Round_item 方法
然后我们在 Round_item 原型的 draw() 方法中创建每一个离屏的小的 canvas。
function Round_item(index, x, y) {
this.index = index;
this.x = x;
this.y = y;
this.useCache = useChache;
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.r = Math.random() * 2 + 1;
this.cacheCtx.width = 6 * this.r;
this.cacheCtx.height = 6 * this.r;
var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
this.color = "rgba(255,255,255," + alpha + ")";
if(useChache){
this.cache();
}
}
有人会产生疑问,为什么这里的 cacheCanvas 画布的宽度要设置为 6 倍的半径?那是因为,我们创建的 cacheCanvas 不仅仅是有圆,还包括圆的阴影,所以我们要将 cacheCanvas 的面积设置得稍微大一些,这样才能将圆带阴影一起剪切到我们的主 Canvas 中。
在 draw() 方法中,我们新创建了 cacheCanvas,并获取到了 cacheCanvas 的上下文环境,然后设置其宽高。
然后我们判断了 useChache 变量的值,也就是说,如果我们将 useChache 设置为 true,也就是使用缓存,我们就调用 this.cache() 方法。接下来,我们来看一下 this.cache() 方法。
# this.cache() 方法
同样的,我们也是在 Round_item 的原型中设置 this.cache() 方法。
在 this.cache() 方法中,我们的主要任务是在每一个 cacheCanvas 中都绘制一个圆。
Round_item.prototype.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();
};
这里需要注意的是,和在 draw() 方法中画的圆不同之处是,要注意这里设置的圆心坐标,是 this.r * 3,因为我们创建的 cacheCanvas 的宽度和高度都是 6 * this.r,我们的圆是要显示在 cacheCanvas 的正中心,所以设置圆心的坐标应该是 this.r * 3,this.r * 3。
# draw() 方法
既然设置了 cacheCanvas,那么我们在 draw() 中,就需要使用 Canvas 的 drawImage 方法将 cacheCanvas 中的内容显示在屏幕上。
Round_item.prototype.draw = function () {
if( !useChache){
content.fillStyle = this.color;
content.shadowBlur = this.r * 2;
content.beginPath();
content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
content.closePath();
content.fill();
}else{
content.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
}
};
这里也是要判断下,如果没有使用缓存的话,还是使用最原始的创建圆的方式。
这样,我们就完成了离屏渲染的优化,我们来一起看一下完整的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
html, body {
margin: 0;
overflow: hidden;
width: 100%;
height: 100%;
cursor: none;
background: black;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var ctx = document.getElementById('canvas'),
content = ctx.getContext('2d'),
round = [],
WIDTH,
HEIGHT,
initRoundPopulation = 80,
useChache = true;
WIDTH = document.documentElement.clientWidth;
HEIGHT = document.documentElement.clientHeight;
ctx.width = WIDTH;
ctx.height = HEIGHT;
function Round_item(index, x, y) {
this.index = index;
this.x = x;
this.y = y;
this.useCache = useChache;
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.cacheCtx.width = 6 * this.r;
this.cacheCtx.height = 6 * this.r;
this.r = Math.random() * 2 + 1;
var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
this.color = "rgba(255,255,255," + alpha + ")";
if(useChache){
this.cache();
}
}
Round_item.prototype.draw = function () {
if( !useChache){
content.fillStyle = this.color;
content.shadowBlur = this.r * 2;
content.beginPath();
content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
content.closePath();
content.fill();
}else{
content.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
}
};
Round_item.prototype.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();
};
function animate() {
content.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in round) {
round[i].move();
}
requestAnimationFrame(animate)
}
Round_item.prototype.move = function () {
this.y -= 0.15;
if (this.y <= -10) {
this.y = HEIGHT + 10;
}
this.draw();
};
function init() {
for (var i = 0; i < initRoundPopulation; i++) {
round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
round[i].draw();
}
animate();
}
init();
</script>
</body>
</html>