前言

从我接触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. ELK-7.3安装部署

    原文 ELK-7.3安装部署 前沿 1.什么是ELK? ELK是由Elasticsearch.Logstash.Kibana 三个开源软件的组成的一个组合体 不懂自行查阅 https://www.el ...

  2. Xcode开发时碰到的问题

    1.打包成功后,发布到蒲公英上,显示"未签名,只能越狱手机可以安装". 出现这个问题,是因为打包的时候签名没有获取到.下面是配置签名的大概步骤. 打包的时候需要点击左上角选择这个设 ...

  3. css识别空格回车符

    新闻发布系统文字需要换行,后台返回数据包含空格.回车符号需要默认显示出来.在父元素加上css样式 :white-space:pre:即可. 例:<div class="white-sp ...

  4. redis 分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  5. webacp4.0

    'use strict';const path = require('path'); var APP_PATH = path.resolve(__dirname, 'src');const webpa ...

  6. CentOS 7.3 环境配置java和tomcat开机启动

    Centos7下添加开机自启动服务和脚本  https://blog.csdn.net/GMingZhou/article/details/78677953 安装部分参考我的博文,仅参考他的开机启动部 ...

  7. 服务器修改静态ip

    ifconfig,看当前网卡名字 设置 ip地址,子网掩码 ,广播地址 ifconfig eth1 172.16.11.25 netmask 255.255.255.0 broadcast 172.1 ...

  8. [HTML知识体系]语义化标签概论

    1.什么是语义化 语义化(Semantic)在HTML5中被大量提起,就是语义化标签向浏览器和开发者展示了它所包裹内容的类型与意思,可是至今我看了好多代码,HTML5新增的语义化标签的使用率还是挺低的 ...

  9. shell 根据路径获取文件名和目录

    path=/dir1/dir2/dir3/test.txt echo ${path##*/} 获取文件名 test.txtecho ${path##*.} 获取后缀 txt #不带后缀的文件名temp ...

  10. POJ 3159 Candies(spfa、差分约束)

    Description During the kindergarten days, flymouse was the monitor of his class. Occasionally the he ...