前言

从我接触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. .Net Core应用RabbitMQ,及简单封装

    首先,还是万分感谢大家能够抽空来阅读我的文章,万分感谢.今天我带来的是.Net Core中应用RabbitMQ,和简单封装.因为昨天的文章里说了今天要写,所以今天一定要写出来.小编翻阅了很多资料,想要 ...

  2. 【转载】Elasticsearch--java操作之QueryBuilders构建搜索Query

    原文地址:https://www.cnblogs.com/pypua/articles/9459941.html package com.elasticsearch; import org.elast ...

  3. 数据库索引原理,及MySQL索引类型(转)

    在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytable表: CREATE TABLE mytable( ID INT NOT NULL, username ) NOT N ...

  4. python3.x 匿名函数lambda_扩展sort

    #匿名函数lambda 参数: 表达式关键字 lambda 说明它是一个匿名函数,冒号 : 前面的变量是该匿名函数的参数,冒号后面是函数的返回值,注意这里不需使用 return 关键字. ambda只 ...

  5. java实现http协议发送和接收数据

    public void sendMessage() throws Exception { System.out.println("调用servlet开始=================&q ...

  6. Spring 讲解(一 )

    1.如何理解 Spring 框架 简单来说,Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架. 因为以前写代码的时候,在使用类对象的时候,经常需要实例化创建(new 出来) ...

  7. jdbc 事物

    package transaction; import jdbc.utils.*; import java.sql.Connection; import java.sql.PreparedStatem ...

  8. Jetson Nano系列教程1:烧写系统镜像

    下载镜像 NVIDIA官方为Jetson Nano Developer Kit (后面统称为Jetson Nano了)提供了SD卡版本的系统镜像,并且根据JetPack版本不断得在更新.所以你可以直接 ...

  9. tar的具体参数和用法!

    tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个.下面的 ...

  10. 浅析DirectX11技术带给图形业界的改变(一) 浅析DirectX11技术带给图形业界的改变【转】

    浅析DirectX11技术带给图形业界的改变(一) 浅析DirectX11技术带给图形业界的改变 前言:2009年10月23日,微软高调发布了其最新一代操作系统——Windows7,这款操作系统相对于 ...