一, 介绍与需求
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 标签
- let canvas = document.getElementById('fire');
- if (canvas.getContext){
- var ctx = canvas.getContext('2d');
- // drawing code here
- } else {
- alert("不支持 Canvas")
- }
第三步: 绘制火花
- var Spark = function (mouse) {
- this.cx = mouse.x;
- this.cy = mouse.y;
- this.x = rand(this.cx - 40, this.cx + 40);
- this.y = rand(this.cy, this.cy + 5);
- this.lx = this.x;
- this.ly = this.y;
- this.vy = rand(1, 3);
- this.vx = rand(-4, 4);
- this.r = rand(0, 1);
- this.life = rand(4, 5);
- this.alive = true;
- this.c = {
- h: Math.floor(rand(2, 40)),
- s: 100,
- l: rand(40, 100),
- a: rand(0.8, 0.9)
- }
- }
- Spark.prototype.update = function () {
- this.lx = this.x;
- this.ly = this.y;
- this.y -= this.vy;
- this.x += this.vx;
- if (this.x <this.cx)
- this.vx += 0.2;
- else
- this.vx -= 0.2;
- this.vy += 0.08;
- this.life -= 0.1;
- if (this.life <= 0) {
- this.c.a -= 0.05;
- if (this.c.a <= 0)
- this.alive = false;
- }
- }
- Spark.prototype.draw = function (ctx) {
- ctx.beginPath();
- ctx.moveTo(this.lx, this.ly);
- ctx.lineTo(this.x, this.y);
- ctx.strokeStyle = "hsla(" + this.c.h + "," + this.c.s + "%," + this.c.l + "%," + (this.c.a / 2) + ")";
- ctx.lineWidth = this.r * 2;
- ctx.lineCap = 'round';
- ctx.stroke();
- ctx.closePath();
- ctx.beginPath();
- ctx.moveTo(this.lx, this.ly);
- ctx.lineTo(this.x, this.y);
- ctx.strokeStyle = "hsla(" + this.c.h + "," + this.c.s + "%," + this.c.l + "%," + this.c.a + ")";
- ctx.lineWidth = this.r;
- ctx.stroke();
- ctx.closePath();
- }
第四步: 绘制火焰
- var Flame = function (mouse) {
- this.cx = mouse.x;
- this.cy = mouse.y;
- this.x = rand(this.cx - 25, this.cx + 25);
- this.y = rand(this.cy - 5, this.cy + 5);
- this.vy = rand(1, 3);
- this.vx = rand(-1, 1);
- this.r = rand(20, 30);
- this.life = rand(3, 6);
- this.alive = true;
- this.c = {
- h: Math.floor(rand(2, 40)),
- s: 100,
- l: rand(80, 100),
- a: 0,
- ta: rand(0.8, 0.9)
- }
- }
- Flame.prototype.update = function () {
- this.y -= this.vy;
- this.vy += 0.05;
- this.x += this.vx;
- if (this.x < this.cx)
- this.vx += 0.1;
- else
- this.vx -= 0.1;
- if (this.r> 0)
- this.r -= 0.1;
- if (this.r <= 0)
- this.r = 0;
- this.life -= 0.15;
- if (this.life <= 0) {
- this.c.a -= 0.05;
- if (this.c.a <= 0)
- this.alive = false;
- } else if (this.life> 0 && this.c.a <this.c.ta) {
- this.c.a += .08;
- }
- }
- Flame.prototype.draw = function (ctx) {
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.r * 3, 0, 2 * Math.PI);
- ctx.fillStyle = "hsla(" + this.c.h + "," + this.c.s + "%," + this.c.l + "%," + (this.c.a / 20) + ")";
- ctx.fill();
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
- ctx.fillStyle = "hsla(" + this.c.h + "," + this.c.s + "%," + this.c.l + "%," + this.c.a + ")";
- ctx.fill();
- }
第五步: 绘制火
- var Fire = function () {
- this.canvas = document.getElementById('fire');
- this.ctx = this.canvas.getContext('2d');
- this.canvas.height = Windows.innerHeight;// Windows.innerHeight
- this.canvas.width = Windows.innerWidth;//Windows.innerWidth
- this.aFires = [];
- this.aSpark = [];
- this.aSpark2 = [];
- this.mouse = {
- x: this.canvas.width * .5,
- y: this.canvas.height * .75,
- }
- this.init();
- }
- Fire.prototype.init = function () {
- // 跳转语句
- if (system.win || system.Mac || system.xll) {
- this.canvas.addEventListener('mousemove', this.updateMouse.bind(this), false);//PC 端
- } else {
- this.canvas.addEventListener('touchmove', this.updateMouse.bind(this), false);// 移动端
- }
- }
- Fire.prototype.run = function () {
- this.update();
- this.draw();
- if (this.bRuning)
- requestAnimationFrame(this.run.bind(this));
- }
- Fire.prototype.start = function () {
- this.bRuning = true;
- this.run();
- }
- Fire.prototype.stop = function () {
- this.bRuning = false;
- }
- Fire.prototype.update = function () {
- this.aFires.push(new Flame(this.mouse));
- this.aSpark.push(new Spark(this.mouse));
- this.aSpark2.push(new Spark(this.mouse));
- for (var i = this.aFires.length - 1; i>= 0; i--) {
- if (this.aFires[i].alive)
- this.aFires[i].update();
- else
- this.aFires.splice(i, 1);
- }
- for (var i = this.aSpark.length - 1; i>= 0; i--) {
- if (this.aSpark[i].alive)
- this.aSpark[i].update();
- else
- this.aSpark.splice(i, 1);
- }
- for (var i = this.aSpark2.length - 1; i>= 0; i--) {
- if (this.aSpark2[i].alive)
- this.aSpark2[i].update();
- else
- this.aSpark2.splice(i, 1);
- }
- }
- Fire.prototype.draw = function () {
- this.ctx.globalCompositeOperation = "source-over";
- this.ctx.fillStyle = "rgba( 15, 5, 2, 1 )";
- this.ctx.fillRect(0, 0, Windows.innerWidth, Windows.innerHeight);
- this.grd = this.ctx.createRadialGradient(this.mouse.x, this.mouse.y - 200, 200, this.mouse.x, this.mouse.y - 100, 0);
- this.grd.addColorStop(0, "rgb( 15, 5, 2 )");
- this.grd.addColorStop(1, "rgb( 30, 10, 2 )");
- this.ctx.beginPath();
- this.ctx.arc(this.mouse.x, this.mouse.y - 100, 200, 0, 2 * Math.PI);
- this.ctx.fillStyle = this.grd;
- this.ctx.fill();
- this.ctx.font = "15em Amatic SC";
- this.ctx.textAlign = "center";
- this.ctx.strokeStyle = "rgb(50, 20, 0)";
- this.ctx.fillStyle = "rgb(120, 10, 0)";
- this.ctx.lineWidth = 2;
- this.ctx.strokeText("", this.canvas.width / 2, this.canvas.height * .72);
- this.ctx.fillText("", this.canvas.width / 2, this.canvas.height * .72);
- this.ctx.globalCompositeOperation = "overlay";//or lighter or soft-light
- for (var i = this.aFires.length - 1; i>= 0; i--) {
- this.aFires[i].draw(this.ctx);
- }
- this.ctx.globalCompositeOperation = "soft-light";//"soft-light";//"color-dodge";
- for (var i = this.aSpark.length - 1; i>= 0; i--) {
- if ((i % 2) === 0)
- this.aSpark[i].draw(this.ctx);
- }
- this.ctx.globalCompositeOperation = "color-dodge";//"soft-light";//"color-dodge";
- for (var i = this.aSpark2.length - 1; i>= 0; i--) {
- this.aSpark2[i].draw(this.ctx);
- }
- }
- Fire.prototype.updateMouse = function (e) {
- // 跳转语句
- if (system.win || system.Mac || system.xll) {//PC 端
- this.mouse.x = e.clientX;
- this.mouse.y = e.clientY;
- } else {// 移动端
- e.preventDefault();// 阻止默认行为
- this.mouse.x = e.changedTouches[0].clientX;
- this.mouse.y = e.changedTouches[0].clientY;
- }
- }
第六步: 调用
- var oCanvas;
- init = function () {
- oCanvas = new Fire();
- oCanvas.start();
- }
- Windows.onload = init;
随机数函数
1 rand = function (min, max) { return Math.random() * (max - min) + min; };
效果如下:
三, PC 端与移动端处理
3.1, 判断是 PC 端还是移动端
- // 平台, 设备和操作系统
- var system = {
- win: false,
- Mac: false,
- xll: false
- };
- // 检测平台
- var p = navigator.platform;
- system.win = p.indexOf("Win") == 0;
- system.Mac = p.indexOf("Mac") == 0;
- system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
- // 跳转语句
- if (system.win || system.Mac || system.xll) {
- this.canvas.addEventListener('mousemove', this.updateMouse.bind(this), false);//PC 端 鼠标移动
- } else {
- this.canvas.addEventListener('touchmove', this.updateMouse.bind(this), false);// 移动端 手指滑动
- }
3.2,PC 端还是移动端的事件处理
- // 跳转语句
- if (system.win || system.Mac || system.xll) {//PC 端
- this.mouse.x = e.clientX;
- this.mouse.y = e.clientY;
- } else {// 移动端
- e.preventDefault();// 阻止默认行为
- this.mouse.x = e.changedTouches[0].clientX;
- this.mouse.y = e.changedTouches[0].clientY;
- }
移动端需禁止缩放
1 <meta name="viewport" content="target-densitydpi=320,width=640,user-scalable=no">
来源: https://www.cnblogs.com/jackson-zhangjiang/p/10369815.html