前言

从我接触canvas的第一天就觉得canvas很有趣,想搞点事情,这几天终于忍不住了,于是他来了。

先看效果

                     

这里我做了四个大家有兴趣可以看完文章,做一个自己喜欢的动画。

思路

开始做之前,我们先分析一下这种粒子动画实现的原理,绘制的内容是由许多个带有颜色像素点构成,每个像素点在画布上都有自己的坐标。首先获取到要绘制的内容的像素点信息的数组(目标数组)例如

[
{x:10, y:20, color: 'rgba(255, 122, 122)'},
{x:11, y:20, color: 'rgba(255, 122, 122)'},
{x:12, y:20, color: 'rgba(255, 122, 122)'},
]

然后我们就可以让这些像素点从某些特定的位置,以某种特定的方式,移动到目标位置,动画就完成了。

实现

1.获取目标数组

我们先说一下 canvas 的 getImageData() ,该方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

  • R - 红色 (0-255)
  • G - 绿色 (0-255)
  • B - 蓝色 (0-255)
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

真实样子是这个样子的

0,1,2,3 4,5,6,7 8,9,10,11
12,13,14,15 16,17,18,19 20,21,22,23

每四个值为一组,用来表示一个像素点的信息,每一个单元格代表一个像素。

先在一个canvas中绘制想要的内容,通过getImageData()获得像素信息,我们发现ImageData 对象的信息和我们想象中的目标数组不大一样,我们要将ImageData对象处理一下,我们将其每四个划分为一组,重新定义索引,例如我们在一个12px宽的画布中,经过分析不难发现坐标与索引之间的关系,分两种情况 n<12(画布的宽度) 时坐标为((n+1)%12, n+1),n>12时坐标为((n+1)%12, parseInt((n+1)/ 12))

0(0,0) 1(0,1) .. n((n+1)%12, n+1) 11(0,11)
.. ..       ..          ..           ..            
     

n((n+1)%12, parseInt((n+1)/ 12))

 

到这里功能是实现了,但是如果操作的内容很大,像素点很多,后期操作的像素点越多性能就越差,有没有什么办法可以稀释一下这些像素呢,当然有!我们可以隔一个像素取一个像素,这样像素点瞬间就减少了一倍,同理我们隔两个隔三个隔n个,这样我们就可以定义一个参数用来控制像素的稀释度

下面的事情就简单了,用代码实来现这一步

/*
* @ ImageDataFormat
* @ param { pixels 需要格式化的ImageData对象, n 稀释度 }
* @ return { Array }
*/ function ImageDataFormat(pixels, n){
    n = n*4
var arr = [], //目标数组
    temPixel = {}, //目标数组中存放像素信息的对象
    x = 0, //像素的x坐标
    y = 0 //像素的y坐标
for (var i=0;i<pixels.data.length;i+=n){
      //过滤纯色背景提高性能,如背景色不可去掉可省略判断
if(pixels.data[i] !== 0 || pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
var index = (i+1) / 4 //每四个划分为一组,重新定义索引
if(index > timeDom.width){
x = index % timeDom.width
y = parseInt(index / timeDom.width)
}else{
x = index
y = 0
}
temPixel = {
R: pixels.data[i],
G: pixels.data[i+1],
B: pixels.data[i+2],
A: pixels.data[i+3],
I:i,
X:x,
Y:y
} arr.push(temPixel)
} }
    return arr }

2.将目标数组绘制到画布上

2.1在画布的指定位置画一个圆(一个像素点)

  /**
* @ drawArc
* @ param{ ctx 画布,,x x坐标,y y坐标,color 颜色}
*/
function drawArc(ctx, x, y, color){
x = x
y = y
ctx.beginPath();
ctx.fillStyle = color
ctx.strokeStyle = color
ctx.arc(x,y,0.5,0,2*Math.PI);
ctx.closePath()
ctx.fill()
}

2.1将点连成线,线构成面

  /**
* 画路径
* @param { points 格式化好的目标数组, crx 画布}
*/
function draw_path(points, ctx){   for(var i=0;i < points.length-1;i++){     var color = 'rgba(' + points[i].R + ',' + points[i].G + ',' + points[i].B + ')', x, y
drawArc(ctx,points[i].X,points[i].Y, color)
}
}

到此我们就画出了动画的其中一帧,下面我们就要让这一帧动起来

2.2动起来

我们的动画进行其实很简单

1.画第一帧

2.清空画布

3.画下一帧

4.在清空

....

但是想让这个动画流畅的进行起来我们还要在了解一下tween(缓动动画), window.requestAnimationFrame()

tween 我们值列举一种其他 形式感兴趣的可以自己查一下

/*
* @ 参数描述
* @ t 动画执行到当前帧所经过的时间
* @ b 起始值
* @ c 总位移值
* @ d 持续时间
*/
function easeInOutExpon(t,b,c,d){
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}

window.requestAnimationFrame()

准备工作做好了可以开工了

我么只需要将前面的函数稍微改动一下他就动起来了

  function ShowTimeInit(pixels, n){
n = 4*n
var arr = [], temPixel = {}, x = 0, y = 0
for (var i=0;i<pixels.data.length;i+=n){
if(pixels.data[i] !== 0 || pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
var index = parseInt ((i+1) / 4)
if(index > timeDom.width){
x = index % timeDom.width
y = parseInt(index / timeDom.width)
}else{
x = index
y = 0
}
temPixel = {
R: pixels.data[i],
G: pixels.data[i+1],
B: pixels.data[i+2],
A: pixels.data[i+3],
I:i,
X:x,
Y:y
} arr.push(temPixel)
} }
var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)}) }
/**
* 画路径
* @param path 路径
*/
function draw_path(points, ctx, step){
ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
var pointX, pointY, randomX, randomY
for(var i=0;i < points.length-1;i++){
switch (mode){
case 'left':
pointX = randomNum(0,0)
pointY = randomNum(0,100)
randomX = 0
randomY = Math.random() + Math.random()*3000
break;
case 'center':
pointX = 80
pointY = 50
randomX = Math.random() + Math.random()*3000
randomY = Math.random() + Math.random()*3000
break;
case 'random':
pointX = 0
pointY = 0
randomX = Math.random() + Math.random()*3000
randomY = Math.random() + Math.random()*3000
break;
case 'flow':
pointX = 0
pointY = 0
randomX = i
randomY = i
break;
} var color = 'rgba(' + points[i].R + ',' + points[i].G + ',' + points[i].B + ')', x, y
x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
drawArc(ctx,x, y, color) }
nowDuration += 1000/60
if(duration <= nowDuration){
window.cancelAnimationFrame(step);
}else{
requestAnimationFrame(function(){draw_path(points, ctx, step)})
} }

附上完整代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style> </style>
<title>电子时钟</title>
</head>
<body>
<canvas id="HidenTime" width="300" height="100" style="display: none"> </canvas>
<canvas id="ShowTime" width="300" height="100"> </canvas>
</body>
<script>
function GetTime(){
this._Hours = ''
this._Minutes = ''
this._Seconds = ''
}
GetTime.prototype = {
constructor: GetTime,
get Hours(){
this._Hours = new Date().getHours()
if(this._Hours > 9){
return this._Hours
}else{
return "0" + this._Hours
}
},
get Minutes(){
this._Minutes = new Date().getMinutes()
if(this._Minutes > 9){
return this._Minutes
}else{
return "0" + this._Minutes
}
},
get Seconds(){
this._Seconds = new Date().getSeconds()
if(this._Seconds > 9){
return this._Seconds
}else{
return "0" + this._Seconds
}
},
formTime:function(){
return this.Hours + ':' + this.Minutes + ':' + this.Seconds
}
}
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
var duration = 3000, nowDuration = 0
var timeDom = document.getElementById("HidenTime")
var time = timeDom.getContext('2d')
var ShowTimeDom = document.getElementById("ShowTime")
var ShowTime = ShowTimeDom.getContext('2d')
time.clearRect(0,0,timeDom.width,timeDom.height);
var nowTime = new GetTime()
var showTime = nowTime.formTime()
var modes = ['left', 'random', 'center', 'flow']
var mode = modes[0]
time.font="50px Verdana";
// 创建渐变
var gradient=time.createLinearGradient(0,0,timeDom.width,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("1.0","red");
// 用渐变填色
time.fillStyle=gradient;
time.fillText(showTime,10,60);
var pixels = time.getImageData(0,0,300,100)
ShowTimeInit(pixels, 2)
setInterval(function(){ mode = modes[randomNum(0,3)]
//mode = modes[3]
time.clearRect(0,0,timeDom.width,timeDom.height);
nowDuration = 0
showTime = nowTime.formTime()
time.fillText(showTime,10,60);
pixels = time.getImageData(0,0,300,100)
ShowTimeInit(pixels, 2)
}, 5000)
function ShowTimeInit(pixels, n){
n = 4*n
var arr = [], temPixel = {}, x = 0, y = 0
for (var i=0;i<pixels.data.length;i+=n){
if(pixels.data[i] !== 0 || pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
var index = parseInt ((i+1) / 4)
if(index > timeDom.width){
x = index % timeDom.width
y = parseInt(index / timeDom.width)
}else{
x = index
y = 0
}
temPixel = {
R: pixels.data[i],
G: pixels.data[i+1],
B: pixels.data[i+2],
A: pixels.data[i+3],
I:i,
X:x,
Y:y
} arr.push(temPixel)
} }
var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)}) }
/**
* 画路径
* @param path 路径
*/
function draw_path(points, ctx, step){
ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
var pointX, pointY, randomX, randomY
for(var i=0;i < points.length-1;i++){
switch (mode){
case 'left':
pointX = randomNum(0,0)
pointY = randomNum(0,100)
randomX = 0
randomY = Math.random() + Math.random()*3000
break;
case 'center':
pointX = 80
pointY = 50
randomX = Math.random() + Math.random()*3000
randomY = Math.random() + Math.random()*3000
break;
case 'random':
pointX = 0
pointY = 0
randomX = Math.random() + Math.random()*3000
randomY = Math.random() + Math.random()*3000
break;
case 'flow':
pointX = 0
pointY = 0
randomX = i
randomY = i
break;
} var color = 'rgba(' + points[i].R + ',' + points[i].G + ',' + points[i].B + ')', x, y
x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
drawArc(ctx,x, y, color) }
nowDuration += 1000/60
if(duration <= nowDuration){
window.cancelAnimationFrame(step);
}else{
requestAnimationFrame(function(){draw_path(points, ctx, step)})
} }
/**
* 画圆
*/
function drawArc(ctx, x, y, color){
x = x
y = y
ctx.beginPath();
ctx.fillStyle = color
ctx.strokeStyle = color
ctx.arc(x,y,0.5,0,2*Math.PI);
ctx.closePath()
ctx.fill()
} /*
* 参数描述
* t 动画执行到当前帧所经过的时间
* b 起始值
* c 总位移值
* d 持续时间
*/
function easeInOutExpon(t,b,c,d){
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
} //生成从minNum到maxNum的随机数
function randomNum(minNum, maxNum) {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * minNum + 1, 10);
break;
case 2:
return parseInt(Math.random() * ( maxNum - minNum + 1 ) + minNum, 10);
break;
default:
return 0;
break;
}
}
</script>
</html>

 总结

当一个新想法出现时,先去github和博客上找一找,看看有没有大佬做过,大佬们的的思路是什么,有什么自己没想到的细节,感觉差不多了在动手去做。

js canvas 粒子动画 电子表的更多相关文章

  1. 打造高大上的Canvas粒子(一)

    HTML5 Canvas <canvas>标签定义图形,比如图表和其他图像,必须用脚本(javascript)绘制图形. 举例:绘制矩形 <script> var c = do ...

  2. canvas学习之粒子动画

    项目地址:http://pan.baidu.com/s/1ccTptc 粒子动画意思就是把一个图片粒子画,然后使用粒子作出动画效果,主要两个问题:一个图片如何粒子化,这里面我们使用canvas的get ...

  3. 带着canvas去流浪系列之九 粒子动画【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  4. 带着canvas去流浪系列之九 粒子动画

    [摘要] canvas实现粒子动画 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 粒子特效 粒子特效一般指密集点阵效果,它并不是canvas独有 ...

  5. 【带着canvas去流浪(9)】粒子动画

    目录 一. 粒子特效 二. 开发中遇到的问题 2.1 卡顿 2.2 轨迹 2.3 复位 2.4 防护层 2.5 二维向量类 三. 实现讲解 3.1 粒子类的update方法 3.2 粒子群的绘制 3. ...

  6. CodePen 作品秀:Canvas 粒子效果文本动画

    作品名称——Shape Shifter,基于 Canvas 的粒子图形变换实验.在页面下方的输入框输入文本,上面就会进行变换出对应的粒子效果文本动画. CodePen 作品秀系列向大家展示来自 Cod ...

  7. css3动画和JS+DOM动画和JS+canvas动画比较

    css3兼容:IE10+.FF.oprea(animation):safari.chrome(-webkit-animation) js+dom:没有兼容问题: js+canvas:IE9+:(性能最 ...

  8. 基于canvas与原生JS的H5动画引擎

    前一段时间项目组里有一些H5动画的需求,由于没有专业的前端人员,便交由我这个做后台的研究相关的H5动画技术. 通过初步调研,H5动画的实现大概有以下几种方式: 1.基于css实现 这种方式比较简单易学 ...

  9. canvas粒子时钟

    前面的话 本文将使用canvas实现粒子时钟效果 效果展示 点阵数字 digit.js是一个三维数组,包含的是0到9以及冒号(digit[10])的二维点阵.每个数字的点阵表示是7*10大小的二维数组 ...

随机推荐

  1. C++大整数类模板

    参考 :http://172.21.85.56/oj/resource/reportdetail?report_id=1678 支持 =.abs().pow().+=.-= *=./=.%=.+.-. ...

  2. 一次spring boot web服务响应缓慢的排查

    使用spring boot搭建了一个web服务,部署在docker容器中.使用中出现了一个性能问题:多次接口请求中,偶尔会出现一次响应非常慢的情况.正常情况下接口的响应时间在10-20ms,偶尔会出现 ...

  3. VS进程附加的使用

    一,附加进程(ctrl+alt+p)调试本地服务器接口(WCF,webapi等)的api 1>接口(WCF,webapi等)的api发布到本地IIS服务器,然后使用附加进程,将你发布的IIS添加 ...

  4. git pull的时候发生冲突的解决方法之“error: Your local changes to the following files would be overwritten by merge”

    今天在使用git pull 命令的时候发生了以下报错 目前git的报错提示已经相关友好了,可以直观的发现,这里可以通过commit的方式解决这个冲突问题,但还是想看看其他大佬是怎么解决这类问题的 在网 ...

  5. tomcat脚本

    !/bin/sh # eg: tomcat.sh start xxx # proc_dir="/usr/local/xxx/tomcat-zc-web/bin" proc_name ...

  6. C#编程--第二天

    一.变量:变量先声明,后赋值,再使用. 语法:变量类型 变量名=值: 变量类型: 分为基本数据类型和引用类 基本数据类型:整型.浮点型.字符型.布尔型 引用类:字符串.日期时间.枚举类型.结构类型 i ...

  7. python 多进程队列数据处理

    # -*- coding:utf8 -*- import paho.mqtt.client as mqtt from multiprocessing import Process, Queue imp ...

  8. Jenkins的安装、部署、启动(完整教程)

    测试环境 Linux系统 Centos 7 安装步骤: 1.安装jdk 我安装的是jdk8,此处就不多说了,自己百度哈,很简单 2.安装jenkins 首先依次执行如下三个命令: 2.1.导入镜像: ...

  9. Spring 讲解(六)

    如何理解 Spring 中的 AOP 一.AOP 的概述 AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理来实现程序功能的统一维护的一种技 ...

  10. mysql中limit 和 limit 与 offset 的用法(效果相同,用法不通过)

    例1,假设数据库表student存在13条数据. 代码示例: 语句1:select * from student limit 9,4 语句2:slect * from student limit 4 ...