Canvas动画(PC端 移动端)
一,介绍与需求
1.1,介绍
canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术。<canvas> 标记和 SVG以及 VML 之间的一个重要的不同是,<canvas> 有一个基于 JavaScript 的绘图 API,而 SVG 和 VML 使用一个 XML 文档来描述绘图。SVG 绘图很容易编辑与生成,但功能明显要弱一些。canvas可以完成动画、游戏、图表、图像处理等原来需要Flash完成的一些功能
1.2,需求
实现特殊的动画效果
二,动画实现
以跟随鼠标/手指移动的火为例
第一步:创建Canvas标签
1 <canvas id="fire"></canvas>
第二步:获取Canvas标签
1 let canvas = document.getElementById('fire'); 2 if (canvas.getContext){ 3 var ctx = canvas.getContext('2d'); 4 // drawing code here 5 } else { 6 alert("不支持Canvas") 7 }
第三步:绘制火花
1 var Spark = function (mouse) { 2 3 this.cx = mouse.x; 4 this.cy = mouse.y; 5 this.x = rand(this.cx - 40, this.cx + 40); 6 this.y = rand(this.cy, this.cy + 5); 7 this.lx = this.x; 8 this.ly = this.y; 9 this.vy = rand(1, 3); 10 this.vx = rand(-4, 4); 11 this.r = rand(0, 1); 12 this.life = rand(4, 5); 13 this.alive = true; 14 this.c = { 15 16 h: Math.floor(rand(2, 40)), 17 s: 100, 18 l: rand(40, 100), 19 a: rand(0.8, 0.9) 20 21 } 22 23 } 24 Spark.prototype.update = function () { 25 26 this.lx = this.x; 27 this.ly = this.y; 28 29 this.y -= this.vy; 30 this.x += this.vx; 31 32 if (this.x < this.cx) 33 this.vx += 0.2; 34 else 35 this.vx -= 0.2; 36 37 this.vy += 0.08; 38 this.life -= 0.1; 39 40 if (this.life <= 0) { 41 42 this.c.a -= 0.05; 43 44 if (this.c.a <= 0) 45 this.alive = false; 46 47 } 48 49 } 50 Spark.prototype.draw = function (ctx) { 51 52 ctx.beginPath(); 53 ctx.moveTo(this.lx, this.ly); 54 ctx.lineTo(this.x, this.y); 55 ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a / 2) + ")"; 56 ctx.lineWidth = this.r * 2; 57 ctx.lineCap = 'round'; 58 ctx.stroke(); 59 ctx.closePath(); 60 61 ctx.beginPath(); 62 ctx.moveTo(this.lx, this.ly); 63 ctx.lineTo(this.x, this.y); 64 ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")"; 65 ctx.lineWidth = this.r; 66 ctx.stroke(); 67 ctx.closePath(); 68 69 }
第四步:绘制火焰
1 var Flame = function (mouse) { 2 3 this.cx = mouse.x; 4 this.cy = mouse.y; 5 this.x = rand(this.cx - 25, this.cx + 25); 6 this.y = rand(this.cy - 5, this.cy + 5); 7 this.vy = rand(1, 3); 8 this.vx = rand(-1, 1); 9 this.r = rand(20, 30); 10 this.life = rand(3, 6); 11 this.alive = true; 12 this.c = { 13 14 h: Math.floor(rand(2, 40)), 15 s: 100, 16 l: rand(80, 100), 17 a: 0, 18 ta: rand(0.8, 0.9) 19 20 } 21 22 23 } 24 Flame.prototype.update = function () { 25 26 this.y -= this.vy; 27 this.vy += 0.05; 28 29 30 this.x += this.vx; 31 32 if (this.x < this.cx) 33 this.vx += 0.1; 34 else 35 this.vx -= 0.1; 36 37 38 39 40 if (this.r > 0) 41 this.r -= 0.1; 42 43 if (this.r <= 0) 44 this.r = 0; 45 46 47 48 this.life -= 0.15; 49 50 if (this.life <= 0) { 51 52 this.c.a -= 0.05; 53 54 if (this.c.a <= 0) 55 this.alive = false; 56 57 } else if (this.life > 0 && this.c.a < this.c.ta) { 58 59 this.c.a += .08; 60 61 } 62 63 } 64 Flame.prototype.draw = function (ctx) { 65 66 ctx.beginPath(); 67 ctx.arc(this.x, this.y, this.r * 3, 0, 2 * Math.PI); 68 ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a / 20) + ")"; 69 ctx.fill(); 70 71 ctx.beginPath(); 72 ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI); 73 ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")"; 74 ctx.fill(); 75 76 }
第五步:绘制火
1 var Fire = function () { 2 3 this.canvas = document.getElementById('fire'); 4 this.ctx = this.canvas.getContext('2d'); 5 this.canvas.height = window.innerHeight;// window.innerHeight 6 this.canvas.width = window.innerWidth;//window.innerWidth 7 8 this.aFires = []; 9 this.aSpark = []; 10 this.aSpark2 = []; 11 12 13 14 this.mouse = { 15 x: this.canvas.width * .5, 16 y: this.canvas.height * .75, 17 } 18 19 20 21 this.init(); 22 23 } 24 Fire.prototype.init = function () { 25 //跳转语句 26 if (system.win || system.mac || system.xll) { 27 this.canvas.addEventListener('mousemove', this.updateMouse.bind(this), false);//PC端 28 } else { 29 this.canvas.addEventListener('touchmove', this.updateMouse.bind(this), false);//移动端 30 } 31 32 33 } 34 Fire.prototype.run = function () { 35 36 this.update(); 37 this.draw(); 38 39 if (this.bRuning) 40 requestAnimationFrame(this.run.bind(this)); 41 42 } 43 Fire.prototype.start = function () { 44 45 this.bRuning = true; 46 this.run(); 47 48 } 49 Fire.prototype.stop = function () { 50 51 this.bRuning = false; 52 53 } 54 Fire.prototype.update = function () { 55 56 this.aFires.push(new Flame(this.mouse)); 57 this.aSpark.push(new Spark(this.mouse)); 58 this.aSpark2.push(new Spark(this.mouse)); 59 60 61 62 for (var i = this.aFires.length - 1; i >= 0; i--) { 63 64 if (this.aFires[i].alive) 65 this.aFires[i].update(); 66 else 67 this.aFires.splice(i, 1); 68 69 } 70 71 for (var i = this.aSpark.length - 1; i >= 0; i--) { 72 73 if (this.aSpark[i].alive) 74 this.aSpark[i].update(); 75 else 76 this.aSpark.splice(i, 1); 77 78 } 79 80 81 for (var i = this.aSpark2.length - 1; i >= 0; i--) { 82 83 if (this.aSpark2[i].alive) 84 this.aSpark2[i].update(); 85 else 86 this.aSpark2.splice(i, 1); 87 88 } 89 90 } 91 92 Fire.prototype.draw = function () { 93 94 this.ctx.globalCompositeOperation = "source-over"; 95 this.ctx.fillStyle = "rgba( 15, 5, 2, 1 )"; 96 this.ctx.fillRect(0, 0, window.innerWidth, window.innerHeight); 97 98 this.grd = this.ctx.createRadialGradient(this.mouse.x, this.mouse.y - 200, 200, this.mouse.x, this.mouse.y - 100, 0); 99 this.grd.addColorStop(0, "rgb( 15, 5, 2 )"); 100 this.grd.addColorStop(1, "rgb( 30, 10, 2 )"); 101 this.ctx.beginPath(); 102 this.ctx.arc(this.mouse.x, this.mouse.y - 100, 200, 0, 2 * Math.PI); 103 this.ctx.fillStyle = this.grd; 104 this.ctx.fill(); 105 106 107 this.ctx.font = "15em Amatic SC"; 108 this.ctx.textAlign = "center"; 109 this.ctx.strokeStyle = "rgb(50, 20, 0)"; 110 this.ctx.fillStyle = "rgb(120, 10, 0)"; 111 this.ctx.lineWidth = 2; 112 this.ctx.strokeText("", this.canvas.width / 2, this.canvas.height * .72); 113 this.ctx.fillText("", this.canvas.width / 2, this.canvas.height * .72); 114 115 116 117 this.ctx.globalCompositeOperation = "overlay";//or lighter or soft-light 118 119 for (var i = this.aFires.length - 1; i >= 0; i--) { 120 121 this.aFires[i].draw(this.ctx); 122 123 } 124 125 this.ctx.globalCompositeOperation = "soft-light";//"soft-light";//"color-dodge"; 126 127 for (var i = this.aSpark.length - 1; i >= 0; i--) { 128 129 if ((i % 2) === 0) 130 this.aSpark[i].draw(this.ctx); 131 132 } 133 134 135 this.ctx.globalCompositeOperation = "color-dodge";//"soft-light";//"color-dodge"; 136 137 for (var i = this.aSpark2.length - 1; i >= 0; i--) { 138 139 this.aSpark2[i].draw(this.ctx); 140 141 } 142 143 144 } 145 146 Fire.prototype.updateMouse = function (e) { 147 //跳转语句 148 if (system.win || system.mac || system.xll) {//PC端 149 this.mouse.x = e.clientX; 150 this.mouse.y = e.clientY; 151 } else {//移动端 152 e.preventDefault();//阻止默认行为 153 this.mouse.x = e.changedTouches[0].clientX; 154 this.mouse.y = e.changedTouches[0].clientY; 155 } 156 157 158 }
第六步:调用
1 var oCanvas; 2 init = function () { 3 4 oCanvas = new Fire(); 5 oCanvas.start(); 6 7 8 } 9 window.onload = init;
随机数函数
1 rand = function (min, max) { return Math.random() * (max - min) + min; };
效果如下:
三,PC端与移动端处理
3.1,判断是PC端还是移动端
1 //平台、设备和操作系统 2 var system = { 3 win: false, 4 mac: false, 5 xll: false 6 }; 7 //检测平台 8 var p = navigator.platform; 9 system.win = p.indexOf("Win") == 0; 10 system.mac = p.indexOf("Mac") == 0; 11 system.x11 = (p == "X11") || (p.indexOf("Linux") == 0); 12 13 //跳转语句 14 if (system.win || system.mac || system.xll) { 15 this.canvas.addEventListener('mousemove', this.updateMouse.bind(this), false);//PC端 鼠标移动 16 } else { 17 this.canvas.addEventListener('touchmove', this.updateMouse.bind(this), false);//移动端 手指滑动 18 }
3.2,PC端还是移动端的事件处理
1 //跳转语句 2 if (system.win || system.mac || system.xll) {//PC端 3 this.mouse.x = e.clientX; 4 this.mouse.y = e.clientY; 5 } else {//移动端 6 e.preventDefault();//阻止默认行为 7 this.mouse.x = e.changedTouches[0].clientX; 8 this.mouse.y = e.changedTouches[0].clientY; 9 }
移动端需禁止缩放
1 <meta name="viewport" content="target-densitydpi=320,width=640,user-scalable=no">