什么是 Canvas?Canvas 是为了解决 Web 页面中只能显示静态图片这个问题而提出的,一个可以使用 JavaScript 等脚本语言向其中绘制图像的 HTML 标签。
img
标签,它可以用来嵌入图像。Audio
和 Video
标签,可以直接将音频和视频资源放在 Web 上,而不需要其他第三方。Canvas
标签。它是一个绘图表面,包含一组丰富的 JavaScript API,这些 API 使你能够动态创建和操作图像及动画。img
对静态图形内容起到了哪些作用,Canvas
就可能对可编写脚本的动态内容起到哪Canvas 已经受到了主流浏览器的支持,并且支持情况良好
直接使用图片,如果需求只是显示一个圆形,那么可以直接使用图片。
使用 div + CSS3 的 border + border-radius 模拟一个圆。
使用 svg。可能对于很多前端来说,svg 和 png、jpg 等其他图片格式是一样的,但其实还是有一定的差别。下面我们会详细介绍 svg。
Canvas + JavaScript 动态画一个圆
那么我们来分析一下以上几种方式的优劣性:
使用图片可以说是以上几种方式中排名倒数第一的了,因为直接使用图片首先会增加一次请求(制作成精灵图另说),其次是不易更改,如果想换一种颜色就需要更换图片,代价太大。
使用 div + CSS3 的方式适用于单个的圆,实现起来比较简单,代价也比较小,但增加了一个没有意义的 DOM 节点,不符合语义化编程规范。
使用 svg 和 Canvas 都可以使用脚本语言来动态写入一个圆。
刚刚我们介绍了 Canvas,那么什么是 svg 呢?
svg(Scalable Vector Graphics,可缩放矢量图形)是基于 XML(可扩展标记语言,标准通用标记语言的子集),用于描述二维矢量图形的一种图形格式。它由 W3C(万维网联盟)制定,是一个开放标准。
简单的说就是,svg 可以用来定义 XML 格式的矢量图形。
因为其本质是 XML 文件,所以 svg 是使用 XML 文档描述来绘图的。和 HTML 一样,如果我们需要修改 svg 文件,可以直接使用记事本打开修改。
Canvas
和 svg
都允许你在浏览器中创建图形,但是它们在根本上是不同的,那么 Canvas 和 svg 有什么根本区别呢?
就如刚刚介绍的那样,svg 本质上是一种使用 XML 描述 2D 图形的语言。
Canvas 通过 JavaScript 来绘制 2D 图形,Canvas 只是一个 HTML 元素,其中的图形不会单独创建 DOM 元素。因此我们不能通过 JavaScript 操控 Canvas 内单独的图形,不能对其中的具体图形进行监控。
在 Canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。
实际上 Canvas 是基于像素的即时模式图形系统,绘制完对象后不保存对象到内存中,当再次需要这个对象时,需要重新绘制;svg 是基于形状的保留模式图形系统,绘制完对象后会将其保存在内存中,当需要修改这个对象信息时,直接修改就可以了。这种根本的区别导致了很多应用场景的不同。
因为 Canvas 通过 JavaScript 可以动态传入参数绘制图形,所以我们可以使用 Canvas 作为容器,通过 JavaScript 动态传入的参数将数据以图表的形式显示出来。
不仅显示更为方便,而且修改数据也同样的简单。同时也可以有一些简单的动画和交互效果,对于可视化的数据展示更为友好。
这些都是传统的 png/jpg
静态显示图片所不能比拟的。
现在的一些数据可视化的 js 库(如 ECharts)大部分都是使用 Canvas
实现的。
下面我们来欣赏一下用 Canvas 制作的几款简单的小游戏~
这是一款小型的赛车游戏,可以使用键盘的方向键来控制赛车的行驶方向,没有按键操作,速度就会变成 0。
这是一款五子棋的小游戏,模拟五子棋游戏规则,游戏开始时一方先走,然后另一方再走,依次循环,直至一方有连成一条线的五个棋子,会自动判断输赢。
就连经典的俄罗斯方块游戏也可以使用 Canvas 来制作。也是需要通过键盘的方向键来控制方块,左右方向键是控制方块移动的方向,下键是加速下落,上键是翻转方块,直至某一行完全被填满就被消除。
还有前一段挺火的你画我猜小游戏也可以通过 Canvas 来实现。保存了鼠标的轨迹,还有清除屏幕和橡皮擦等功能。也可以将其保存为一张图片。
这是一款迷宫类的小游戏,同样是通过键盘的方向键来控制小球的移动,最终到达某个地点就会判定通过游戏。
相信很多的营销活动大家都做过,Canvas 也可以写活动页面哦(๑•̀ㅂ•́)و,✧这是很多公司的营销策略
很常见的活动页面,某宝店铺的常见套路,模拟转盘抽奖。点击按钮,转盘转动,然后转盘停止,指针落在哪个区域就提示中奖的奖品。
另一个比较常见的活动页面是刮刮乐的页面,在刮刮乐的区域,鼠标会显示为硬币的形状,然后按住鼠标并拖动,经过区域就会显示出最底层的图片,同时上层图片消失。模拟刮刮乐效果,刮开一定比例面积之后“刮奖”完成,返回回调函数。
Canvas 还可以做一些小特效哦,这些小特效可以装饰你 的网站,使它变得更加精致~
这个特效也可以在活动页面中用到。逼真的模拟了纸片下落的过程,随机出现的纸片有随机生成的颜色,然后模拟重力下落过程。
这个其实是很不错的,只不过 gif 图片看得不是特别清楚。随机生成的大小随机的圆形或方形粒子组成指定的文字,粒子会自动缩放,给人闪烁的感觉。
这个特效也是由粒子组合成文字的特效,只不过文字的内容是当前的时间,因为是 gif 图,所以大家看不到和鼠标的交互,这里粒子散开是因为我鼠标进行了点击,就是点击屏幕,粒子就会散开,点击的时间越长,粒子就越分散。当松开鼠标之后,所有的粒子就又全部回到原位~
试想一下,如果你的个人网站中的当前时间使用了这样一个小特效,是不是会让访客觉得很有趣呢~٩(๑>◡<๑)۶
同样的小特效还有这个 loading,使用 canvas 制作一个这样的 loading,也会给你的网站增添亮点。
上述几种应用场景都只是带大家了解一下,不做过多的介绍,当然也还有其他的应用场景
因为 Canvas 的特性,所以如果你的网站想要一个炫酷的背景,那么 Canvas 无疑是最好的选择,让我们一起来欣赏一下 canvas 可以做出哪些炫酷的背景。
这个特效是使用 three.js 和 Canvas 制作出的 3D 线条,随机生成的线条构建成 3D 的立体空间,还有和鼠标的交互,鼠标的移动会使得 3D 空间移动。
这个气泡背景也是很赞的,随机生成透明度不同的气泡,气泡不断移动,渐变色的背景也在不断变换,给人以梦幻的感觉。
这个的变换频率其实没有这么快,两次变换中间是有几秒钟的停留时间的,因为制作的 gif 大小有要求,所以中间的给截掉了,只保留变换的部分。
这个特效给人的感觉是和钻石表面类似的菱角感觉,然后背景也是使用的渐变的颜色。一定时间就会变换一次。QQ 的登录框就是和这个类似的特效。
这个背景是黑客帝国的代码雨特效,可能男程序员会比较喜欢,代码从上向下下落的效果。
我个人是很喜欢这个特效的,前一段时间也将其作为我的个人博客的背景,该特效的背景是一个渐变色,随机生成的“小星星”会从下向上移动,最重要的是和鼠标有互动。鼠标经过的地方会产生“星星”并自动和其他的星星连成线。
这个特效也是很不错的,模拟星空,随机生成的 “星星” 会有位移,透明度也在不断变化,会有 “流星” 随机从上向下掉落,并会有小尾巴的效果~
这个特效是以黑色为背景,彩色的随机实心 + 空心圆圈构成,所有的圈圈用直线相连,并且一直移动。
这个相交线特效其实也很简单,随机生成的线条在移动,相交的地方为小圆点。
带大家欣赏完这么多的特效之后,你是不是已经想使用 Canvas 来装饰你的个人网站了呢?心动不如行动,下面来带大家一起从零开始,一点一点分析怎么制作属于你自己的炫酷网页背景特效~ε==(づ′▽`)づ
当我只在页面上写一个 Canvas 标签时,将其背景颜色设置为黑色,会是什么效果呢?
我们打开浏览器来看一下:
在上面的例子中页面上只有一个 Canvas,没有设置宽高,那么会自动创建一个 300 \* 150
的画布(单位默认为 px)
我们来试一下这三种方式。有的人会问了,这不是很简单的么,还有介绍的必要吗?这就和我们听数学课是一样的,那些很简单的知识点你就不注意听,然后 10 分钟过后,一脸懵逼的不知道老师在讲什么,或者说是遇到问题了不知道错在哪,往往也都是基础的问题没有仔细听~
width
、height
我们先来看一下直接使用 HTML 属性来设置 width、height
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#canvas {
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
</canvas>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI * 2, true);
context.closePath();
context.fillStyle = 'rgb(255,255,255)';
context.fill();
</script>
</body>
</html>
我们设置 Canvas 画布的宽度为 400,高度为 400,背景颜色为黑色(在 HTML 属性中直接设置宽度和高度是可以不加单位的,默认单位是 px
)。在 Canvas 上画了一个圆心坐标为 100px、100px,半径为 50px 的白色的圆。来看一下浏览器中的显示效果:
width
、height
还是上面那个例子,这次我们将宽度和高度使用 CSS 来设置:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#canvas {
background: #000;
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<canvas id="canvas">
</canvas>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI * 2, true);
context.closePath();
context.fillStyle = 'rgb(255,255,255)';
context.fill();
</script>
</body>
</html>
我们来看下浏览器中的显示效果:
OMG ヽ(;´Д`)ノ,怎么会是这个样子,我明明是要画一个圆啊,怎么变成椭圆了,是不是我代码写的有问题?
检查下代码,没问题呀o((⊙﹏⊙))o.那么为什么会显示成这个样子呢?
原来是因为如果使用 CSS 来设置宽高的话,画布就会按照 300 * 150 的比例进行缩放,也就是将 300 * 150 的页面显示在 400 * 400 的容器中
width
、height
那我们再来看一下如果使用 JS 来设置宽高会是神马效果呢~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#canvas {
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas">
</canvas>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI * 2, true);
context.closePath();
context.fillStyle = 'rgb(255,255,255)';
context.fill();
</script>
</body>
</html>
所以我们尽量使用 HTML 的width 和 height 属性或者直接使用 JS 动态来设置宽高,不要使用 CSS 设置。
在前面的例子中,我们已经创建了一个 Canvas 画布,那么第二步要做的就是获取到 Canvas 的上下文环境,对应的语法为:canvas.getContext(contextType, contextAttributes)
;
第二个参数并不是经常用到,所以这里就不给大家介绍了,有兴趣的可以查阅 MDN 文档~
通常在创建好一个 Canvas 标签的时候,我们要做的第一步就是要先获取到这个 Canvas 的上下文对象:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
使用过 PS 的应该都会知道在 PS 中有路径的概念,在 Canvas 中也有路径的概念。只不过和 PS 中的路径不同的是,PS 中的路径是矢量的,而 Canvas 中的路径不是。下面我们来看一下有哪些创建路径的方法:
fill()
填充路径stroke()
描边arc()
创建圆弧rect()
创建矩形fillRect()
绘制矩形路径区域strokeRect()
绘制矩形路径描边clearRect()
在给定的矩形内清除指定的像素arcTo()
创建两切线之间的弧/曲线beginPath()
起始一条路径,或重置当前路径moveTo()
把路径移动到画布中的指定点,不创建线条lineTo()
添加一个新点,然后在画布中创建从该点到最后指定点的线条closePath()
创建从当前点回到起始点的路径clip()
从原始画布剪切任意形状和尺寸的区域quadraticCurveTo()
创建二次方贝塞尔曲线bezierCurveTo()
创建三次方贝塞尔曲线 isPointInPath()
如果指定的点位于当前路径中,则返回 true,否则返回 false看完了上述方法你是不是有点不知所措,一下子这么多方法(╬ ̄皿 ̄)=○
你可以把上面的表格作为一个“字典”,在下面的代码中如果遇到不认识的方法可以查找一下,一回生,二回熟。
下面我将上面的方法分为以下几部分来给大家介绍下。
我们先从最基本的开始,使用 Canvas 画一个点。其实画一个点也就相当于画一个半径为 1 的圆,那我们就可以改造这一节开始的例子,将半径由 50 变为 1。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#canvas {
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas">
</canvas>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.arc(100, 100, 1, 0, Math.PI * 2, true);
context.closePath();
context.fillStyle = 'rgb(255,255,255)';
context.fill();
</script>
</body>
</html>
让我们来看一下效果:
有没有看到左上部分有一个白色的点,没有看到?没有看到的同学点开大图看一下(/_\)
细心的小伙伴可能会发现我们改动了哪里:
context.arc(100, 100, 1, 0, Math.PI * 2, true);
将第三个参数由 50 改为了 1,聪明的你一定可以猜出来 arc() 这个方法的作用了。\( ̄︶ ̄)/,稍后我们再介绍 arc() 函数。
先来看一下在获取完 Canvas 的上下文环境之后,我们又做了哪些操作:
context.beginPath(); // 起始一条路径,或重置当前路径
context.arc(100, 100, 1, 0, Math.PI * 2, true); // 创建弧/曲线
context.closePath(); // 创建从当前点回到起始点的路径
context.fillStyle = 'rgb(255,255,255)'; // 设置或返回用于填充绘画的颜色、渐变或模式
context.fill(); // 填充当前绘图(路径)
我们可以总结出,使用 Canvas
绘制图像的步骤:
通过使用 Canvas 绘制一个点,我们了解了在 Canvas 中绘图的大致步骤,下面我们来看一下刚刚提到的 arc()
方法。
arc()
方法创建弧/曲线(用于创建圆或部分圆)
context.arc(x,y,r,sAngle,eAngle,counterclockwise);
x
:圆心的 x
坐标y
:圆心的 y
坐标r
:圆的半径sAngle
:起始角,以弧度计(弧的圆形的三点钟位置是 0 度)eAngle
:结束角,以弧度计counterclockwise
:可选。规定应该逆时针还是顺时针绘图。false
为顺时针,true
为逆时针比如我们想画一个顺时针的四分之一圆,应该怎么写呢?
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI * 0.5, false);
context.strokeStyle="white";
context.stroke();
我们先来看一下浏览器中的效果:
是不是你想要的效果呢(๑´ㅂ`๑)
其实只要找好起始角和结束角就成功一半了呢。
beginPath
并没有设置 closePath
,所以这就不是一条闭合的路径。我们加上 cloasePath
看一下效果。这是因为 stroke()
和fill()
的区别,根据上面的两个例子,我们很容易看出这两个函数的区别:一个是描边
,一个是填充
。
stroke()
:描边
fill()
:填充
我们可以通过 strokeStyle
属性 和 fillStyle
属性来设置描边和填充的颜色。这里不仅可以设置单一的颜色,还可以设置渐变。
下面我们来绘制一条线。
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.moveTo(50,50);
context.lineTo(100,100);
context.strokeStyle = '#fff';
context.stroke();
在绘制直线的例子中,我们使用了
moveTo(x,y)
:把路径移动到画布中的指定点,不创建线条
lineTo(x,y)
:添加一个新点,然后在画布中创建从该点到最后指定点的线条
这里需要注意以下几点:
如果没有moveTo
,那么第一次lineTo
的就视为 moveTo
每次lineTo
后如果没有 moveTo
,那么下次 lineTo
的开始点为前一次 lineTo
的结束点。
也就是这种情况:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.lineTo(200, 200);
context.lineTo(200, 100);
context.lineTo(100,50);
context.strokeStyle = '#fff';
context.stroke();
我们没有设置 moveTo
,而是设置了三个 lineTo
,这也是可以的,将三个 lineTo
设置的点依次连接就好~
效果如下:
在绘制了直线之后,我们来看一下怎么给绘制的直线添加样式:
lineCap
设置或返回线条的结束端点样式 lineJoin
设置或返回两条线相交时,所创建的拐角类型lineWidth
设置或返回当前的线条宽度miterLimit
设置或返回最大斜接长度我们来看下这些 属性 是怎么使用的。
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.moveTo(10,10);
context.lineTo(100,100);
context.lineWidth = 10;
context.lineCap = 'round';
context.strokeStyle = '#fff';
context.stroke()
我绘制了一条由点 (10,10) 到点 (100,100) 的直线,然后将其宽度设置为 10,并且加上“圆角”的效果。
这里我们要注意区分哪些是方法哪些是属性,如果是方法,只需要在括号中传入参数就可以;如果是属性,那么我们就要使用等号给其赋值。有的时候你会奇怪,为什么我这么设置了但是却没有效果呢?很有可能是你将方法和属性搞混了哦 (●゚ω゚●)
在了解了最基本的绘制点、线的方法之后,我们来看一下如何绘制一个矩形。
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.fillStyle = '#fff';
context.fillRect(10, 10, 100, 100);
context.strokeStyle = '#fff';
context.strokeRect(130, 10, 100, 100);
这里我们使用下面的方法:
fillRect(x,y,width,height)
:绘制一个实心矩形strokeRect(x,y,width,height)
:绘制一个空心矩形 fillStyle()
和strokeStyle()
来设置填充的颜色和描边的颜色。上面几个函数教大家怎么绘制点、线、以及圆形和矩形,都是通过先创建路径,然后再使用fill()
或 stroke()
进行填充或者描边。
下面我们再具体看一下都可以给路径设置哪些属性来改变其样式。
fillStyle
设置或返回用于填充绘画的颜色、渐变或模式strokeStyle
设置或返回用于笔触的颜色、渐变或模式shadowColor
设置或返回用于阴影的颜色shadowBlur
设置或返回用于阴影的模糊级别shadowOffsetX
设置或返回阴影距形状的水平距离shadowOffsetY
设置或返回阴影距形状的垂直距离fillStyle
和 strokeStyle
这两个属性我们一直在使用,所以对于它们我们不再作过多的介绍。设置阴影我们用到的是 shadowBlur
这个属性。.
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.beginPath();
context.arc(100,100,50,0,2*Math.PI,false);
context.fillStyle = '#fff';
context.shadowBlur = 20;
context.shadowColor = '#fff';
context.fill()
同样的方(tao)法(lu),我们只要在fill()
方法之前设置模糊指数(shadowBlur
)和颜色(shadowColor
)就可以了。让我们来看一下浏览器中的效果:
在暗色背景中有一个亮色的圆并且加了阴影效果,是不是很像发光的月亮呢(●´∀`●)ノ
我们先来看一下怎么设置渐变:
createLinearGradient()
创建线性渐变(用在画布内容上)createPattern()
在指定的方向上重复指定的元素createRadialGradient()
创建放射状/环形的渐变(用在画布内容上)addColorStop()
规定渐变对象中的颜色和停止位置其中绘制渐变主要用到了 createLinearGradient()
方法,我们来看一下这个方法: context.createLinearGradient(x0,y0,x1,y1);
x0
:开始渐变的 x
坐标y0
:开始渐变的 y
坐标x1
:结束渐变的 x
坐标y1
:结束渐变的 y
坐标这是设置比如说我们下一个粉色到白色的由上向下的渐变:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
var grd = context.createLinearGradient(100,100,100,200);
grd.addColorStop(0,'pink');
grd.addColorStop(1,'white');
context.fillStyle = grd;
context.fillRect(100,100,200,200);
我们看一下浏览器中的效果:
可以看出,createLinearGradient()
的参数是两个点的坐标,这两个点的连线实际上就是渐变的方向。我们可以使用 addColorStop()
方法来设置渐变的颜色。
gradient.addColorStop(stop,color);
stop
:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置 color
:在结束位置显示的 CSS 颜色值var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
var grd = context.createLinearGradient(0,0,0,400);
grd.addColorStop(0,'rgb(255, 0, 0)');
grd.addColorStop(0.2,'rgb(255, 165, 0)');
grd.addColorStop(0.3,'rgb(255, 255, 0)');
grd.addColorStop(0.5,'rgb(0, 255, 0)');
grd.addColorStop(0.7,'rgb(0, 127, 255)');
grd.addColorStop(0.9,'rgb(0, 0, 255)');
grd.addColorStop(1,'rgb(139, 0, 255)');
context.fillStyle = grd;
context.fillRect(0,0,400,400);
在了解完了最基本的绘制路径和设置样式之后,我们来看一下怎么来进行图形的变换。
我们先来看一下图形转换都有哪些方法:
scale()
缩放当前绘图至更大或更小rotate()
旋转当前绘图translate()
重新映射画布上的 (0,0) 位置transform()
替换绘图的当前转换矩阵setTransform()
将当前转换重置为单位矩阵,然后运行 transform()我们来看一下怎么使用 Canvas 实现缩放的功能,绘制一个矩形;放大到 200%,再次绘制矩形;放大到 200%,然后再次绘制矩形;放大到 200%,再次绘制矩形:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.strokeStyle = 'white';
context.strokeRect(5,5,50,25);
context.scale(2,2);
context.strokeRect(5,5,50,25);
context.scale(2,2);
context.strokeRect(5,5,50,25);
只是使用scale()
方法就可以实现缩放的效果,我们再来看一下浏览器中的显示情况:
可以看到,在设置 scale()
方法之后再设置的矩形,无论是线条的宽度还是坐标的位置,都被放大了。并且 scale()
的效果是可以叠加的,也就是说,我们在上面的例子中使用了两次 scale(2,2)
那么,最后一个矩形相对于第一个矩形长和宽,以及坐标的位置就放大了 4 倍。
其实在图形变换中,只要掌握了一种,其他的图形变换方式就会迎刃而解了。我们再来看一下旋转的例子吧。
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 400;
var cy = canvas.height = 400;
context.fillStyle = 'white';
context.rotate(20*Math.PI/180);
context.fillRect(70,30,200,100);
我们使用的是rotate()
方法 context.rotate(angle)
;
在刚刚的例子中,我们将画布旋转了 20°,然后再画了一个矩形。
通过上述两个例子,我们会发现一个特点,在进行图形变换的时候,我们需要画布旋转,然后再绘制图形。
这样的结果是,我们使用的图形变换的方法都是作用在画布上的,既然对画布进行了变换,那么在接下来绘制的图形都会变换。这点是需要注意的。
比如我对画布使用了 rotate(20*Math.PI/180)
方法,就是将画布旋转了 20°,然后之后绘制的图形都会旋转 20°。
Canvas
还有一个经常用的方法是drawImage()
。
drawImage()
向画布上绘制图像、画布或视频
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
img
:规定要使用的图像、画布或视频sx
:可选。开始剪切的 x 坐标位置sy
:可选。开始剪切的 y 坐标位置swidth
:可选。被剪切图像的宽度sheight
:可选。被剪切图像的高度x
:在画布上放置图像的 x 坐标位置y
:在画布上放置图像的 y 坐标位置width
:可选。要使用的图像的宽度(伸展或缩小图像)height
:可选。要使用的图像的高度(伸展或缩小图像)经过上面对 Canvas 常见方法的介绍,相信你也可以绘制一些基本的图形了,你看到的那些炫酷的效果都是由这些简单的图形构成的。在下一节我将会带大家分析怎么使用这些最基本的元素来组成炫酷的特效~~
各种特效的源码地址:链接
我们分别从这三个方面来分析,一个炫酷的网页背景都有哪些通性。
我们要制作的是一个可以作为背景的特效,那么首要的条件就是这是一个背景,我们这里所说的背景是一个全屏的背景,充满你整个屏幕,我们来分析下 “全屏” 背景应该是怎样的。
在普通的页面制作中,我们观察设计稿,可以看出,背景往往是纯色的或者是渐变的,再或者就是有规律的可以平铺的图形。
首先是因为用户使用的显示屏的尺寸我们是不知道的,甚至不知道是移动设备还是 PC,综合起来,能够显示我们网站的设备就太多了。
为了适配所有的设备,尽可能让所有的设备都能够显示出相同的效果,不能相差太远,这种情况我们就只能将背景设置为单一的颜色,或者两种颜色(渐变),再或者一些可平铺的图案,让其在各个终端下都有相同的显示效果。
说到渐变,我个人是比较喜欢将背景设置为渐变的,关注我的朋友可能都知道,我文章的封面图一般都是一个渐变的背景 + 文字。简洁却不简单,因为每一个渐变图案都是我自己精挑细选的。
也有人问我我的渐变颜色的取值都是从哪里去的呢?是我有独特的审美吗,(/□\*)
我也希望是我自己做出来的啦,但是,答案,当然不是的啦,都是在网站上 down 的。网址可以分享给大家:链接
这个网站可以自己生成渐变色,你的配色也可以跟大家分享,可以保存为图片,也可以导出为 CSS 样式。
我们可以从这个网站上找到喜欢的配色,然后导出为 CSS 样式使用。
我们看上面的两个例子,背景都是使用了渐变元素。第一个例子是一个气泡的效果。背景使用的是随机生成的颜色,使用我们上节介绍的 Canvas 设置渐变的方式。
下面的星空背景的渐变实际上不是使用 Canvas 写的,只是使用 CSS 写出的效果。实现的方式是:
然后我们将 body
的颜色设置为黑色到蓝色的由上向下的渐变:
background: linear-gradient(to bottom,#000000 0%,#5788fe 100%);
效果如下:
接下来我们要设置一个全屏的遮罩,将这个遮罩的背景色设置为红色,然后使用 CSS3 的 animation
属性,使用 animation
改变其透明度,由 0 变为 0.9。
.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: .9;
}
}
效果就和上面动态的效果一样。
第二个关键词是“炫酷”,我们要做到炫酷的特点,就是要让别人看到你的博客背景之后,发出 “哎呦,不错哦” 的感叹!
那么你的博客背景有怎样的特点才会让用户眼前一亮呢?我分析主要是因为以下两个原因:
因为网页技术的发展,最先开始只能显示单一的文字,然后慢慢开始支持图片资源,直到 HTML5 才支持音频和视频等媒体资源,并且现在大部分我们看到的资源都是图片 —— 也就是静态资源。
所以,页面上有元素在“动”,都将会吸引我们的眼球。
怎么能让页面上的元素动起来呢?我们有以下几种方式:
gif
图CSS3
动画js
控制svg
以上几种方式都可以创建动画,但是怎样的动画更能引人入胜呢?
这就需要符合另一个特点:“随机”。设置为“随机” 的动画有什么好处呢?
人都有一种心理,一旦找到事物发展的规律,便对其失去了兴趣。
也就是说,如果你的动画是一个规律的,并且规律是简单可寻的,那么用户在看过一次之后,找到了其中的规律,第二次再看的时候便不会再对其感兴趣。
相反,我们就可以写出让用户每一次打开都不一样的特效,这样用户会感觉到“新奇”,便会对你的网站感兴趣。
使用 gif 图大家都知道,只能是有规律的“动”,并且 gif 图片的尺寸不宜过大,在我们的网页背景中,基本上是不会用到的。
CSS3 实现的动画效果,也是只能做有规律的“动”,并且 CSS 只能操纵单个的 DOM 元素,一旦元素到达一定的数量,代价是比较大的。
所以我们选择 js + Canvas
来实现“随机”的“动”。
具体的写法我们将会在后面的章节介绍。我们先来接着看第三个特点:效果。
这里我们说的效果主要是与鼠标之间的交互效果。
与鼠标之间有互动的效果主要是产生用户行为的反馈,比如在网页制作中,我们经常使用 hover 变色表示用户的鼠标在元素上方悬停。这就是用户行为的一种反馈。
我们经常使用的与鼠标之间的交互效果主要有两种:
记得当年 QQ 空间盛行的时候,就有很多这种鼠标跟随的效果,鼠标滑过的路径,能够生产一个长长的尾巴,甚是招人喜欢。同样的,如果我们能实现鼠标跟随的效果,也是极好的。
我们又拿出了这个特效 (ಥ_ಥ)
在这个例子中,我们可以看出,在鼠标经过的地方会出现 “星星” 连成线的效果。
用户很喜欢这种鼠标跟随的效果,个人觉得就是因为它使得网站的显示效果和用户的行为产生了联系,使用户的行为得到了反馈。
还有一种经常见到的效果是数据差的效果,比如:
这是锤子官网的一个特效,鼠标移动到哪哪就会下沉,并且如果你仔细看的话就会发现,上面的月份数字和底部的图片不是在一个层级上的,更加有立体的感觉,这就是视觉差的特效。
这种特效不需要用 Canvas,只需要 CSS 就可以实现,实现方式也不是本节重点,有需要的可以私聊。
我们来总结一下,炫酷的网页背景特效有哪些特点:
如果只是一个纯色或者渐变的背景,肯定会显得有点单调,我们还需要在渐变的基础上加一点 “料”,而这些 “料”通常都是粒子特效。 那么“粒子特效” 都有什么特点呢?
粒子特效这些年还是比较流行的,好多地方都可以看出使用了粒子特效。那么粒子特效是什么呢?
百度百科中对粒子特效定义如下:
将无数的单个粒子组合使其呈现出固定形态,借由控制器,脚本来控制其整体或单个的运动,模拟出现真实的效果。
上面的例子是一种常见的粒子特效,使用若干粒子构成文字 “Hello World”,并且每一个粒子都在运动,和物理学的 “粒子” 概念类似。
粒子特效的首要特点是数量多,在物理学中,粒子是能够以自由状态存在的最小物质组成部分,所以粒子的第一个特点就是数量多。
粒子特效的第二个特点是运动,正是因为组成物体的粒子在不断运动,我们才能看到物体在不断运动。
粒子特效第三个特点是随机,排列有整齐排列之美,凌乱有凌乱之美,整齐排列的可以直接平铺背景实现,直接使用 img 图片就可以。
但是要想有随机效果使用 img 图片就不可以了,所以我们主要使用 Canvas 实现随机粒子效果。各项参数都是随机生成的。
这个例子已经引用好多遍了╥﹏╥…但是它真的是很有特点啊,炫酷特效的通性全部都占有 o(▽)q
在这个例子中,我们可以将背景上的小 “星星” 看成粒子。粒子的数量是可以自己设置的,位置是随机出现的,大小也是随机生成的,包括透明度也是随机的,这样基本上每一个粒子都是独一无二的。
然后给粒子设置阴影以营造发光的特效,粒子在不断地“动”,做上升运动。
为什么设置随机粒子特效会受欢迎呢?我们来分析一下。首先是我们生成的每一个粒子都是独一无二的,并且每一次刷新位置都是随机的。这种效果是使用其他方式绘制图形都实现不了的(使用 svg 也是可以的,但是本小册不对 svg 绘图做过多的介绍ヘ(;´Д`ヘ))。
现在我们来一起实现一个随机粒子特效。
首先,我们需要一个全屏的 Canvas 画布。
<!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'),
WIDTH,
HEIGHT;
WIDTH = document.documentElement.clientWidth;
HEIGHT = document.documentElement.clientHeight;
ctx.width = WIDTH;
ctx.height = HEIGHT;
</script>
</body>
</html>
我们使用 WIDTH
、HEIGHT
两个常量储存屏幕宽度和高度信息,我们习惯使用大写来表示改变量为常量,不可变,将屏幕宽度和高度信息储存在常量中是因为我们在稍后还会用到。
这时,你应该得到一个全屏的并且为黑色的 Canvas。
Round_item
类在创建了一个全屏的 Canvas 之后,我们来创建单个的 Round_item
类。
首先我们 Round_item
类需要有什么参数呢?我们要设置的是位置随机、透明度随机、半径随机的圆。为了区分不同的圆,我们还应该设置一个唯一的 index
参数。
根据上面这些可以得出我们的 Round_item
类:
function Round_item(index,x,y) {
this.index = index;
this.x = x;
this.y = y;
this.r = Math.random() * 2 + 1;
var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
this.color = "rgba(255,255,255," + alpha + ")";
}
这里我们使用了构造函数的方式来创建单个的圆,我们还需要一个变量initRoundPopulation
来设置 round 的个数,然后我们便可以通过for
循环创建出 initRoundPopulation
个圆。
draw()
方法在设置了单个的Round_item
类之后,我们还要给每一个 round 设置 draw()
方法,所以我们需要将 draw()
方法设置在 Round_item
的原型中,这样我们创建出来的每一个Round_item
实例对象都拥有了 draw()
方法。
draw()
方法的内容就是我们第 3 节讲到的画圆的方式,这里也就不再过多的说明了,忘记的快去第 3 节补习补习ヽ(;´Д`)ノ
Round_item.prototype.draw = function () {
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();
};
然后我们就需要设置初始化 init() 函数了,在 init() 函数中,我们的主要任务是创建单个的 round,然后使用其 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();
}
}
至此,我们已经完成了随机粒子的实现,完整的代码如下:
<!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;
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.r = Math.random() * 2 + 1;
var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
this.color = "rgba(255,255,255," + alpha + ")";
}
Round_item.prototype.draw = function () {
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();
};
function init() {
for(var i = 0; i < initRoundPopulation; i++ ){
round[i] = new Round_item(i,Math.random() * WIDTH,Math.random() * HEIGHT);
round[i].draw();
}
}
init();
</script>
</body>
</html>
animate()
函数其实,Canvas 制作动画是一个不断擦除再重绘的过程,跟最原始实现动画的方式类似。在纸片上画每一帧,然后以很快的速度翻动小本本,就会有动画的效果。
现在我们实现动画需要在很短的时间内不断的清除内容再重新绘制,新的图形和原先清除的图形之间有某种位置关系,速度足够快的话,我们就会看到动画的效果。
所以我们需要一个 animate()
函数,这个函数的作用是帮助我们形成动画,我们在这个函数中首先需要清除当前屏幕,这里的清除函数用到的是content.clearRect()
方法。
我们先来看一下 canvas 的 content.clearRect()
方法:
context.clearRect(x,y,width,height);
x
:要清除的矩形左上角的 x 坐标
y
:要清除的矩形左上角的 y 坐标
width
:要清除的矩形的宽度,以像素计
height
:要清除的矩形的高度,以像素计
在刚刚的分析中可以得出,我们需要清除的区域是整个屏幕,所以content.clearRect()
的参数就是 content.clearRect(0, 0, WIDTH, HEIGHT)
;,这里我们就用到了之前获取的屏幕宽度和高度的常量:WIDTH
和 HEIGHT
。这样我们就将屏幕上的所有内容都清除了。
清除了屏幕内容之后我们就要重新绘制图形,重新绘制的图形是需要和原图形之间有一定的关系,我们先制作一个简单的效果 —— 粒子匀速上升。粒子匀速上升,也就是 y 坐标在不断地变化,既然是匀速的,那么也就是在相同的时间位移是相同的。
我们将粒子位移的变化函数move()
写在 Round_item
的原型上。稍后我们再实现。
重新绘制完图形之后,我们就完成了清除屏幕内容再重新绘制新的图形的任务。那么还需要有一个步骤 —— “ 不断”,要想实现动画的效果,就需要 “不断” 地进行清除再重绘,并且中间间隔的时间还不能过长。
这时你可能会想到使用 js 的 setTimeout()
方法,但是 setTimeout
和setInterval
的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器 UI 线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
我们需要使用另外一个函数 —— requestAnimationFrame()
。
window.requestAnimationFrame()
方法告诉浏览器,你希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
requestAnimationFrame()
函数可以说是专门用来写动画的。那么 requestAnimationFrame()
有什么优点呢?
编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms。
requestAnimationFrame
采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
所以我们就使用 requestAnimationFrame()
函数递归的调用 animate()
函数来实现动画的效果。
function animate() {
content.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in round) {
round[i].move();
}
requestAnimationFrame(animate);
}
move()
函数使用 move()
函数来改变 round 的 y 坐标。那么我们就来实现一下。
经过上面的分析,move()
写起来是不是很简单呢?
Round_item.prototype.move = function () {
this.y -= 0.15;
if (this.y <= -10) {
this.y = HEIGHT + 10;
}
this.draw();
};
init()
中加入 animate()
init()
中加入 animate()
函数。<!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;
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.r = Math.random() * 2 + 1;
var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
this.color = "rgba(255,255,255," + alpha + ")";
}
Round_item.prototype.draw = function () {
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();
};
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>
本节我们一起来看一下 Canvas 是怎么和我们的鼠标互动的。
然后我们分析一下这个效果:鼠标移动,会在经过的地方创建一个圆,圆的半径由小变大,达到某个固定大小时该圆消失。圆的颜色也是在随机变化的(gif 图片时间较短,效果不明显)。
首先我们还是要创建并获取 Canvas 元素,相信大家对此步骤应该很熟悉了吧。
这里也顺带将需要的参数直接写好了,我们将一些可以控制的变量直接写在参数中,这样在后面就可以获取参数直接使用。设置参数主要是为了更改方便。
<!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 = []; // 存放圆的数组
</script>
</body>
</html>
我们就创建一个黑色的全屏 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()
函数中将圆显示出来。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()
函数。从函数名就可以看出这个函数的作用,的确,我们需要在该函数中写动画。
写动画的主要思想是 —— 清除屏幕再重新绘制,这里的animate()
函数也是这样的。
我们先来清除屏幕。
ctx.clearRect(0, 0, WIDTH, HEIGHT);
接着使用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);
我们来看下完整的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);
};
以上,我们就写完了一个完整的鼠标跟随效果的例子,让我们来看一下主要的有哪些步骤:
我们来看一下这个例子的完整代码:
<!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>
作为一个网站的用户,在浏览网页的时候,你会喜欢怎样的网页背景呢?
因为网站还是以阅读为主,所以网站的背景颜色要适合阅读,最好还是设置为传统的 “白纸黑字”,使用浅色颜色作为背景,同时饱和度不宜过高,最好设置透明度。
并且背景颜色最好是 1~2 种颜色,不要设置过多的颜色,不然会影响阅读。
背景颜色可以直接使用 CSS 样式设置,不需要使用 Canvas。
为什么大家都喜欢粒子特效呢?首先是因为粒子的颗粒度可以设置得比较小,不会影响阅读,如果你将一张很大的图片设置为背景,那么网页上还有其他的部分,背景肯定是会被遮挡的,这样用户就看不到图片的全部内容。
所以设置为粒子特效,用户可以看到粒子的形状颜色,这样即使其他的粒子被遮挡,用户也会知道被遮挡的部分是什么内容。
其次是因为粒子的随机性,可以设置随机的透明度、随机的坐标、随机的颜色以及随机的大小,可以使得设置的每一个粒子都不同,并且每一次刷新页面,就会又重新生成新的不同的例子。用户永远不知道在哪一个位置有一个怎样的粒子。
大多数用户比较喜欢动效,但是对于网页的背景来说,动作的幅度又不能太大,动作也不要过于复杂,只是一些简单的位移并且动作的幅度也要小一点,让用户的潜意识里面知道这些粒子在动就可以,不能使用户的全部注意力都在粒子上面而忽视了网页的内容。
用户一般还喜欢自己的操作能够得到网页的响应,所以我们可以设置鼠标跟随的效果或者视觉差的效果,加上和鼠标交互的特效,会使用户感到你的网站与众不同。
一般在游戏中这个优化方式会经常使用,但是在我们的背景特效中不经常使用,这个优化方式是将经常移动的元素和不经常移动的元素分层,避免不必要的重绘。
比如在游戏中,背景不经常变换和人物这些经常变换的元素分成不同的层,这样需要重绘的资源就会少很多。
transform
特性缩放画布left
、top
这些 CSS 属性来写动画的话,那么会触发整个像素渲染流程 —— paint
、layout
和 composition
。 transform
中的 translateX/Y
来切换动画,你将会发现,这并不会触发 paint
和 layout
,仅仅会触发 composition 的阶段。transform
调用的是 GPU 而不是 CPU。离屏渲染的主要过程就是将一个一个的粒子先在屏幕之外创建出来,然后再使用 drawImage()
方法将其“放入”到我们的主屏幕中。
在了解了思想之后,我们就来实现一下吧!b( ̄▽ ̄)d
我们首先要在全局设置一个变量 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>