HTML5简单入门系列(九)
前言
上篇本来应该就是最后一篇了,但是楼主总觉得没有写上一个简单应用,不算是完整的学习系列。所以增加一篇关于动画的应用,对一个开源动画的介绍(很基础,非楼主原创)。
本篇介绍一个基于Canvas的发光加载动画(这里可下载源码)。算是对之前系列的各个知识点的一个总结吧。
我们先看看最终的效果截图:
新建一个html和一个js文件
html文件引入该js,并加了一个背景黑色,大体如下这样:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>HTML5 Canvas发光Loading动画DEMO演示</title> 6 <style> 7 body { 8 background: #000; 9 } 10 </style> 11 </head> 12 <body> 13 <canvas id="c"></canvas> 14 <script src="js/index.js"></script> 15 </body> 16 </html>
在画图之前,我们要确定一下简单的数据结构。
看效果图可以知道,我们是要画弧线,所有的动画都是基于这个弧线来操作的,那我们就要确定该弧线的坐标、半径、弧度起始点等。
要让其动起来,即旋转起来,我们需要旋转速度(弧度),当前旋转角度等。
我们整理成如下变量:
1 var c = document.getElementById('c'), 2 ctx = c.getContext('2d'), 3 cw = c.width = 400, 4 ch = c.height = 300, 5 rand = function (a, b) { return ~~((Math.random() * (b - a + 1)) + a); },//获取a到b之间的整数值 6 dToR = function (degrees) { return degrees * (Math.PI / 180); },//角度变弧度 7 circle = { 8 x: (cw / 2) + 5, 9 y: (ch / 2) + 22,//圆心坐标 10 radius: 90,//半径 11 speed: 2,//旋转速度(角度) 12 rotation: 0,//当前角度 13 angleStart: 270,//圆弧起始弧度 14 angleEnd: 90,//结束弧度 15 hue: 220,//色调 16 thickness: 18,//边缘粗细 17 blur: 25//模糊度 18 }, 19 particles = [],//亮点数组 20 particleMax = 100,//亮点个数
19-29行的变量示例中增加其他效果时会用到,这里可以暂时忽略。
画弧线
该弧线带有一个渐变效果,我们会用到的APIs有arc(),createLinearGradient()等,方法如下:
1 renderCircle = function () { 2 ctx.save();//保存当前作图上下文 3 ctx.translate(circle.x, circle.y);//移动坐标原点到圆心位置 4 5 ctx.beginPath(); 6 ctx.arc(0, 0, circle.radius, dToR(circle.angleStart), dToR(circle.angleEnd), true); 7 ctx.lineWidth = circle.thickness; 8 9 var gradient1 = ctx.createLinearGradient(0, -circle.radius, 0, circle.radius); 10 gradient1.addColorStop(0, 'hsla(' + circle.hue + ', 60%, 50%, .25)'); 11 gradient1.addColorStop(1, 'hsla(' + circle.hue + ', 60%, 50%, 0)'); 12 13 ctx.strokeStyle = gradient1;//弧线是渐变色 14 ctx.stroke(); 15 ctx.restore();//画完弧线之后,画其他东西之前,先恢复作图上下文 16 },
这里就不截图看效果了,就是一条渐变弧线。
旋转
这里旋转其实就很简单了,只要不停地旋转角度,在画弧线之前调用旋转API rotate()就可以了。下边是代码:
1 updateCircle = function () { 2 if (circle.rotation < 360) { 3 circle.rotation += circle.speed; 4 } else { 5 circle.rotation = 0; 6 } 7 }
我们可以在画弧线的方法中加入旋转角度(renderCircle 方法第四行加上下边这句即可):
ctx.rotate(dToR(circle.rotation));//旋转角度,此处是动画动起来的地方。
说到这里,其实关于动画的内容就写的差不多了,只要一个定时器不停地调用这两个方法就好了。
不过,要注意一点,每次重新画图之前我们都要将canvas清空,我们调用clearRect或者统一写一个初始化方法即可,如下:
1 clear = function () { 2 ctx.globalCompositeOperation = 'destination-out'; 3 ctx.fillStyle = 'rgba(0, 0, 0, .1)'; 4 ctx.fillRect(0, 0, cw, ch); 5 ctx.globalCompositeOperation = 'lighter'; 6 }
定时器统一调用方法:
1 loop = function () { 2 clear(); 3 updateCircle(); 4 renderCircle(); 5 }
设置定时器:
setInterval(loop, 20);
现在的效果已经出来了:
剩下的就是给弧线增加效果了。
该示例中为了更好地展示效果,设置了弧线边缘,头部发亮及小亮点显隐等多个效果,园友可运行代码查看效果。
完整的js代码如下:
1 /* super inefficient right now, could be improved */ 2 3 //http://www.html5tricks.com/html5-canvas-shine-loading.html 4 5 var c = document.getElementById('c'), 6 ctx = c.getContext('2d'), 7 cw = c.width = 400, 8 ch = c.height = 300, 9 rand = function (a, b) { return ~~((Math.random() * (b - a + 1)) + a); },//获取a到b之间的整数值 10 dToR = function (degrees) { return degrees * (Math.PI / 180); },//角度变弧度 11 circle = { 12 x: (cw / 2) + 5, 13 y: (ch / 2) + 22,//圆心坐标 14 radius: 90,//半径 15 speed: 2,//旋转速度(角度) 16 rotation: 0,//当前角度 17 angleStart: 270,//圆弧起始弧度 18 angleEnd: 90,//结束弧度 19 hue: 220,//色调 20 thickness: 18,//边缘粗细 21 blur: 25//模糊度 22 }, 23 particles = [],//亮点数组 24 particleMax = 100,//亮点个数 25 26 //更新旋转角度 27 updateCircle = function () { 28 if (circle.rotation < 360) { 29 circle.rotation += circle.speed; 30 } else { 31 circle.rotation = 0; 32 } 33 }, 34 //画弧线 35 renderCircle = function () { 36 ctx.save();//保存当前作图上下文 37 ctx.translate(circle.x, circle.y);//移动坐标原点到圆心位置 38 ctx.rotate(dToR(circle.rotation));//旋转角度,此处是动画动起来的地方。 39 ctx.beginPath(); 40 ctx.arc(0, 0, circle.radius, dToR(circle.angleStart), dToR(circle.angleEnd), true); 41 ctx.lineWidth = circle.thickness; 42 43 var gradient1 = ctx.createLinearGradient(0, -circle.radius, 0, circle.radius); 44 gradient1.addColorStop(0, 'hsla(' + circle.hue + ', 60%, 50%, .25)'); 45 gradient1.addColorStop(1, 'hsla(' + circle.hue + ', 60%, 50%, 0)'); 46 47 ctx.strokeStyle = gradient1;//弧线是渐变色 48 ctx.stroke(); 49 ctx.restore();//画完弧线之后,画其他东西之前,先恢复作图上下文 50 }, 51 //弧线边缘效果 52 renderCircleBorder = function () { 53 ctx.save(); 54 ctx.translate(circle.x, circle.y); 55 ctx.rotate(dToR(circle.rotation)); 56 ctx.beginPath(); 57 ctx.arc(0, 0, circle.radius + (circle.thickness / 2), dToR(circle.angleStart), dToR(circle.angleEnd), true); 58 ctx.lineWidth = 2; 59 60 var gradient2 = ctx.createLinearGradient(0, -circle.radius, 0, circle.radius); 61 gradient2.addColorStop(0, 'hsla(' + circle.hue + ', 100%, 50%, 0)'); 62 gradient2.addColorStop(.1, 'hsla(' + circle.hue + ', 100%, 100%, .7)'); 63 gradient2.addColorStop(1, 'hsla(' + circle.hue + ', 100%, 50%, 0)'); 64 65 ctx.strokeStyle = gradient2; 66 ctx.stroke(); 67 ctx.restore(); 68 }, 69 //弧线顶点发亮 70 renderCircleFlare = function () { 71 ctx.save(); 72 ctx.translate(circle.x, circle.y); 73 ctx.rotate(dToR(circle.rotation + 185)); 74 ctx.scale(1, 1); 75 ctx.beginPath(); 76 ctx.arc(0, circle.radius, 30, 0, Math.PI * 2, false); 77 ctx.closePath(); 78 var gradient3 = ctx.createRadialGradient(0, circle.radius, 0, 0, circle.radius, 30); 79 gradient3.addColorStop(0, 'hsla(330, 50%, 50%, .35)'); 80 gradient3.addColorStop(1, 'hsla(330, 50%, 50%, 0)'); 81 ctx.fillStyle = gradient3; 82 ctx.fill(); 83 ctx.restore(); 84 }, 85 //发亮延伸 86 renderCircleFlare2 = function () { 87 ctx.save(); 88 ctx.translate(circle.x, circle.y); 89 ctx.rotate(dToR(circle.rotation + 165)); 90 ctx.scale(1.5, 1); 91 ctx.beginPath(); 92 ctx.arc(0, circle.radius, 25, 0, Math.PI * 2, false); 93 ctx.closePath(); 94 var gradient4 = ctx.createRadialGradient(0, circle.radius, 0, 0, circle.radius, 25); 95 gradient4.addColorStop(0, 'hsla(30, 100%, 50%, .2)'); 96 gradient4.addColorStop(1, 'hsla(30, 100%, 50%, 0)'); 97 ctx.fillStyle = gradient4; 98 ctx.fill(); 99 ctx.restore(); 100 }, 101 //创建亮点 102 createParticles = function () { 103 if (particles.length < particleMax) { 104 particles.push({ 105 x: (circle.x + circle.radius * Math.cos(dToR(circle.rotation - 85))) + (rand(0, circle.thickness * 2) - circle.thickness), 106 y: (circle.y + circle.radius * Math.sin(dToR(circle.rotation - 85))) + (rand(0, circle.thickness * 2) - circle.thickness), 107 vx: (rand(0, 100) - 50) / 1000, 108 vy: (rand(0, 100) - 50) / 1000, 109 radius: rand(1, 6) / 2, 110 alpha: rand(10, 20) / 100 111 }); 112 } 113 }, 114 //更新已有亮点的坐标用于动态更随展示 115 //同时降低透明度,删除透明度过低(先创建的点调用该函数的次数最多,也就最容易变低)的亮点。 116 updateParticles = function () { 117 var i = particles.length; 118 while (i--) { 119 var p = particles[i]; 120 p.vx += (rand(0, 100) - 50) / 750; 121 p.vy += (rand(0, 100) - 50) / 750; 122 p.x += p.vx; 123 p.y += p.vy; 124 p.alpha -= .01; 125 126 if (p.alpha < .02) { 127 particles.splice(i, 1) 128 } 129 } 130 }, 131 renderParticles = function () { 132 var i = particles.length; 133 while (i--) { 134 var p = particles[i]; 135 ctx.beginPath(); 136 ctx.fillRect(p.x, p.y, p.radius, p.radius); 137 ctx.closePath(); 138 ctx.fillStyle = 'hsla(0, 0%, 100%, ' + p.alpha + ')'; 139 } 140 }, 141 clear = function () { 142 ctx.globalCompositeOperation = 'destination-out'; 143 ctx.fillStyle = 'rgba(0, 0, 0, .1)'; 144 ctx.fillRect(0, 0, cw, ch); 145 ctx.globalCompositeOperation = 'lighter'; 146 } 147 loop = function () { 148 clear(); 149 updateCircle(); 150 renderCircle(); 151 renderCircleBorder(); 152 renderCircleFlare(); 153 renderCircleFlare2(); 154 createParticles(); 155 updateParticles(); 156 renderParticles(); 157 } 158 159 160 ctx.shadowBlur = circle.blur; 161 ctx.shadowColor = 'hsla(' + circle.hue + ', 80%, 60%, 1)'; 162 ctx.lineCap = 'round'; 163 164 setInterval(loop, 20);
小结
本篇没有什么内容,只是一个简单应用。
其实在上篇状态的保存和恢复中那个示例就是一个简单动画了。
其实HTML5中动画也不算难,简单说就是画图+定时刷新,所以重点还是在HTML5 APIs的应用及与其他现有技术的交叉使用。