前言
? 相比于多年前对网页的要求,现在的网页在原本要求的基础上更追求网页特效,网站上合理的网页特效不仅吸引了用户的眼光,也增加了趣味性。
? 说到网页特效,便会想到 HTML 5 中的新增的 canvas 标签。那如何灵活的运用canvas 标签为网页添加简单效果呢?
Canvas
-
canvas 标签是 HTML 5 中的新标签
-
canvas 它自己没有行为,相当于一块画布,通过绘图 API使用脚本(JavaScript)绘制图形
-
canvas 标签的用法
- 定义图形,比如图表和其他图像。
- 定义图形容器,必须使用脚本来绘制图形。
-
canvas 默认是一个 300*150 的画布,可通过html属性 'width','height'来设置canvas的宽高
? Ps: 不能使用 css 属性来设置宽高。css 属性设置的宽高会使 canvas 内的图像按 300*150 的比例放大/缩小
网页特效
? 网页特效说到底就是在页面上绘制一些图行,并且让图形动起来,或者是在页面上添加一些鼠标监听事件,让图形可以根据鼠标行为展现出相对于的动画效果。
➡️ 举个 ?
❓如何实现如图所示的网页动画效果
? 按照个人理解,绘制网页动画的思路应该分为以下几步:
1. 绘制画布,向 HTML5 页面添加 canvas 元素。
-
在页面中直接添加 canvas 标签
复制代码
-
通过js获取到 canvas 标签并设置 canvas 标签
// 获取画布 let canvas = document.getElementById('canvas'); // 获取 canvas 2d 上下文 let ctx = canvas.getContext('2d'); // 设置画布的大小 canvas.width = document.documentElement.clientWidth; canvas.height = document.documentElement.clientHeight; 复制代码
canvas.getContext(contextType, contextAttributes);
getContext() 方法返回canvas 的上下文,如果上下文没有定义则返回 null .? context 是一个封装了很多绘图功能的对象? getContext("2d") 对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。复制代码
2. 分析动画中的图形组成
? 由图分析可以看出,这个动画主要的组成图形是圆点和随机组成的三角形,而三角形也不外乎是由圆点和线组成。可得知,这个动画其实也就是使用了 canvas 标签图形API中的绘制线条和圆形的方法
-
canvas 中画圆
context.arc(x, y, radius, starAngle,endAngle, anticlockwise) x: 圆心的 x 坐标 y:圆心的 y 坐标 radius : 半径 starAngle :开始角度 endAngle:结束角度 anticlockwise :是否逆时针(true)为逆时针,(false)为顺时针复制代码
// 起始一条路径,或重置当前路径(开始画圆) ctx.beginPath(); // 设置实心圆填充颜色 ctx.fillStyle = "white"; // 设置圆阴影颜色 ctx.shadowColor = "rgba(251, 255, 0, 0.5)"; // 设置阴影的模糊级别 ctx.shadowBlur = 20; // 画圆 ctx.arc(50, 50, 4, 0, 2 * Math.PI, false); // 创建从当前点回到起始点的路径(结束画圆) ctx.closePath(); // 填充当前绘图 ctx.fill();复制代码
-
canvas 中画线
- moveTo(x,y):把路径移动到画布中的指定点,不创建线条,可理解为线条的起点
- lineTo(x,y):添加一个新点,然后在画布中创建从该点到最后指定点的线条,可理解为线条的终点
- 每次画线都从 moveTo 的点到 lineTo 的点(起点和终点连线便画出了一条直线)
// 设置线条的宽度 ctx.lineWidth = 0.6; // 设置线条的颜色 ctx.strokeStyle = 'rgba(255,255,255, 0.5)'; ctx.moveTo(50, 50); ctx.lineTo(100, 60); // 绘制已定义的路径 ctx.stroke();复制代码
fill() : 填充当前的图像(路径),一般是在画实心图形时使用
stroke() : 实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径,一般是在画线条时使用
3. 使用代码绘制出动画中的图形
-
绘制三角形
三角形是由三个圆点和三条线组成
// 画圆 ctx.beginPath(); ctx.arc(50, 50, 1.5, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); // 画线 ctx.moveTo(50, 50); ctx.lineTo(100, 100); ctx.stroke(); // 画圆 ctx.beginPath(); ctx.arc(100, 100, 2, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); // 画线 ctx.moveTo(100, 100); ctx.lineTo(150, 60); ctx.stroke(); // 画圆 ctx.beginPath(); ctx.arc(150, 60, 1.5, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); // 画线 ctx.moveTo(150, 60); ctx.lineTo(50, 50); ctx.stroke();复制代码
这样三点三线就画出了一个三角形
4. 找出图形绘制规律,封装动态绘制图形的方法
通过上面的代码可以找到绘制三角形的一个规律
-
假设把所有圆点放在一个数组里,数组里存放着每个点的 x 坐标和 y 坐标
// 新建一个空数组 let dots = []; // 根据上面的代码我们可以得到三角形存放三个点的数组如下 dots = [(50, 50), (100, 100), (150, 60)];复制代码
-
假设把所有线的起点和终点分别放在一个数组里,数组里存放着每条线起点/终点的 x 坐标和 y 坐标
// 新建两个空数组 let moveDots = []; let toDots = []; // 根据上面的代码我们可以得到三角形存放三条线起点/终点的数组如下 moveDots = [(50, 50), (100, 100), (150, 60)]; toDots = [(100, 100), (150, 60), (50, 50)];复制代码
-
这样图形的绘制规律就一目了然了
// 通过上面的三个数组,可以得到规律如下: moveDots[i] = dots[i]; toDots[i] = dots[i + 1]; // ? 或许会觉得这个规律不对呀,toDots中最后一个点是跟dots中的第一个点相同的呀。 // ?是的没错,假设如果只是绘制一个三角形的画那确实是toDots中最后一个点是跟dots中的第一个点相同 // ?但在绘制多个相连的三角形的情况下,两个相连三角形之间会共用一个点,所以以上规律是成立的复制代码
-
得到绘制规律后,就可以封装绘制图形的方法了
? 创建绘制图形
class Graphics { constructor(x, y) { this.x = x; this.y = y; this.r = Math.random() * 10 / 2; this.mx = Math.random(); this.my = Math.random(); this.alpha = ( Math.floor(Math.random() * 10) + 1) / 2; } }复制代码
? 动态绘制圆形
Graphics.prototype.drawCricle (ctx) { ctx.beginPath(); ctx.fillStyle = "white"; ctx.shadowColor = "rgba(251, 255, 0, 0.5)"; ctx.shadowBlur = 20; ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false); ctx.closePath(); ctx.fill(); }复制代码
? 动态绘制线条
Graphics.prototype.drawLine (ctx, dot) { let dx = this.x - dot.x; let dy = this.y - dot.y; let d = Math.sqrt(dx * dx + dy * dy); // 通过条件限制画布上线条的数量 if (d < 150) { ctx.lineWidth = 0.6; ctx.strokeStyle = 'rgba(255,255,255, 0.5)'; ctx.moveTo(this.x, this.y); ctx.lineTo(dot.x, dot.y); ctx.stroke(); } }复制代码
? 封装绘图方法
function draw () { // 清空画布 ctx.clearRect(0, 0, WIDTH, HEIGHT); for (let i = 0; i < dot.length; i++) { dot[i].drawCricle(ctx); for (j = i + 1; j < dot.length; j++) { dot[i].drawLine(ctx, dot[j]) } } // 运用了浏览器专门为动画提供的API,通过将循环函数作为参数传入,该API会不断进行重绘形成动画效果 requestAnimationFrame(draw) }复制代码
5. 图形画好后剩下的就是让图形动起来了
- ? 假设把画布看成是一个直角坐标系,有 x 轴和 y 轴,那些图形都是由坐标系中的点组合而成,我们可以通过改变每个点的 x 轴和 y 轴的位置从而达到移动的效果。
- ? 动画说到底就是改变canvas图形API中的 x 和 y 这两个参数的值
? 三角形动画
function move (w, h) { // 判断是否超出画布大小 this.mx = (this.x < w && this.x > 0) ? this.mx : (-this.mx); this.my = (this.y < h && this.y > 0) ? this.my : (-this.my); // 随机改变 x 和 y 的值 this.x += this.mx / 2; this.y += this.my / 2; }复制代码
至此,就实现的上述图中所示的动态效果了。
浏览器动画实现方法
? setTimeout 和 setInterval
-
最原始的方法就是使用window.setTimout()或者window.setInterval()通过不断更新元素的状态位置等来实现动画,前提是画面的更新频率要达到每秒60次才能让肉眼看到流畅的动画效果。
-
但我们都知道,页面上不断的跑着定时器是影响网页性能的。
-
现在的浏览器为了性能,切换页面时,浏览器会把定时器停掉,等再次回到页面时就会出现动画卡顿现象
? requestAnimationFrame
-
此方法告诉浏览器执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用
-
此API是专门用来在浏览器实现动画的,所以浏览器对其也进行了优化,相比setTimeout和setInterval的形式来说,requestAnimationFrame()性能更好
-
在大多数浏览器里,当运行在后台标签页或者隐藏的里时,requestAnimationFrame() 会暂停调用以提升性能和电池寿命
总结
? 通过对 Canvas 网页效果的学习,个人觉得完成一个动画效果大概的思路如下:
- 分析动画中大概是由哪些基本图形组成的
- 使用js绘制出动画中的基本图形
- 找出图形在动画中的绘制规律
- 通过动态改变 x 和 y 值使图形动起来
? 很多复杂的动画效果其实分解到最后都是最本质的点和线的问题,无论是想自己写动画还是还原现有的动画,如果要按照上诉思路去实现的话,应该都会比较好实现
? 功能的实现大部分都离不开清晰的思路,所以在做每个动画之前,理清思路后实现起来就会更得心应手了
? 本文只是针对学习这个动画效果实现过程的一个心得小记,canvas 还有很多很有趣的东西值得我们去学习,包括图形的API,文中提到的这些显然只是冰山一角,多看多想多练,才能体会到其中的精髓所在