最近瞎逛的时候发现了一个超炫的粒子进度效果,有多炫呢?请擦亮眼镜!

 

粗略一看真的被惊艳到了,而且很实用啊有木有!这是 Jack Rugile 写的一个小效果,源码当然是有的。聪慧如你,肯定觉得这个东西so easy 要看啥源码,给我3分钟我就写出来了吧。所以你的思路可能是:

分步实现

1)进度条的实现没什么好说的,简单的一个 fillRect(0,0,long,20),long和20是分别进度条的长宽。然后每帧动画调用前将画布清除clearRect(0,0,canvas.width,canvas.height)。做出来应该是这样的(点击启动/暂停动画):

 

2)进度条色彩的变化。这个也简单,颜色渐变嘛,fillStyle = createLinearGradient() 就行了吧。不对哦,是颜色随时间变化,每一帧内的进度条颜色一样的哦。理所当然就能搞出一句:fillStyle = rgba(f(t),f(t),f(t),1),f(t)是随时间变化的函数。然而,这些只知道rgba的哥们,发现怎么调也调不出这样的渐变效果,rgb变化哪一个都会造成颜色明暗变化,卡壳了吧,这里估计要卡掉5%的人。要保持亮度不发生变化,这里要用到hsla这种颜色格式,就是妹子们自拍修图时常用的色调/饱和度/亮度/透明度。对照进度条的效果,明显我们只要改色调就OK了。

ctx.fillStyle = 'hsla('+(hue++)+', 100%, 40%, 1)'; 

结果可能是这样的(点击启动/暂停动画):

 

3)接下来进入正题,要做粒子效果了。粒子很多,观察力不好或者没掌握方法的同学这里就要歇菜啦(此处应有博主爽朗的笑声,哈哈哈~)。对于元素数量巨大的效果,我们应该尽可能缩小观察范围,只观察一个或者一组元素,找出单体的规律。多看几次,就能发现单个粒子是先向上运动一阵子然后掉下去,单个粒子的x轴应该是不变的。对于粒子集合来说,每个粒子的x坐标递增,就能产生我们需要的效果了。这里推荐同学们去看一下MDN的例程,超好玩的ball(好玩、ball?嘿嘿~):https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Advanced_animations

这里我们每帧只添加一个粒子:

var raf = null,
c = document.createElement('canvas'),
parent = document.getElementById('canvas-wrapper-test3');
c.width = 400;
c.height = 100;
c.id = 'canvas-test3';
parent.appendChild(c);
var ctx = c.getContext('2d'),
hue = 0, //色调
vy = -2, //y轴速度
par = [], //粒子数组
x = 0, //进度条当前位置
draw = function () {
var color;
   ctx.clearRect(0,0,c.width,c.height);
x += 3; //进度条速度为每帧3个像素
   hue = (x > 310) ? 0 : hue;
//颜色渐变为每帧1色调
  color = 'hsla('+(hue++)+', 100%, 40%, 1)' ;
par.push({ //用数组模拟队列
px: x + 40,
   py: 50,
   pvy: vy,
   pcolor: 'hsla('+(hue+30)+', 100%, 70%, 1)',
  });
  x = (x > 310) ? 0 : x; //进度条到右侧后返回
ctx.fillStyle = color;
  ctx.fillRect(45, 40, x, 20);
var n = par.length;
  while(n--){
//切记要随机差异化粒子y轴速度,否则就变成一根抛物线了
   par[n].pvy += (Math.random()+0.1)/5;
  par[n].py += par[n].pvy;
   if (par[n].py > c.height ) {
   par.splice(n, 1); //掉到画布之外了,清除该粒子
   continue;
   }
  ctx.fillStyle = par[n].pcolor;
   ctx.fillRect(par[n].px, par[n].py, 5, 5);
  }
raf = window.requestAnimationFrame(draw);
};
raf = window.requestAnimationFrame(draw);

虽然简单,但效果还是出来了(点击启动/暂停动画):

 

至此,这个动画效果基本完成了,后续要做的就是优化了:

1)增加粒子数量,现在我们每帧要push多个粒子进去,这样数量上就上来了。

2)应该直接调用fillRect绘制小矩形代替圆形,有些筒子可能会真的用arc画一个粒子,囧。。。这里稍微提点常识,计算机绘图中所有曲线都是由直线构成的,要画一个圆就相当于调用了相当多次的画线功能,性能消耗非常大。在粒子这么小的情况下,是圆是方只有瞎子才能分得清了,所以我们应该直接调用fillRect绘制小矩形代替圆形。这个也是canvas绘图里面常用的优化方法哦~

3)增加随机化效果。现在xy起始坐标都跟进度条紧密联系在一起。我们每次生成几个粒子的话,粒子初始坐标应该在一定范围浮动,另外粒子的大小、颜色也应该要在小范围内随机化。颜色相对进度条颜色有一定滞后的话,效果会更加自然。

4)上面说到x方向不动,但是如果x方向增加一点抖动效果的话会更自然生动。

5)画布颜色混合选项设置线性叠加:globalCompositeOperation = 'lighter',这样在粒子重叠的时候颜色会有叠加的效果。这个是在源码上看到的,大牛就是细节会做得比别人好的家伙!关于这个属性的具体解释可以看看这位"大白鲨"的实验,中文的!http://www.cnblogs.com/jenry/archive/2012/02/11/2347012.html

总结一下

想要实现一个效果,首先我们要简化模型,可以分成色彩的变化、位置的变化、大小的变化等,还有就是将某个因子独立出来看,通过各种抽茧剥丝的手法去找到效果后面的数学模型,然后编程去实现它。艺术总是源于生活,所以在做时候应该好好考虑是否应该加入惯性、弹性、重力这些效果,这些物理特性反映到效果中的话,会更加自然逼真。

都总结了,那完事了?

NO!NO!NO!

接下来才是我想要说的重点!上面的代码效果优化之后,老大看到效果觉得还不错哦,加到新项目去吧。。。然后就是啪啦啪啦ctrlC ctrlV?好吧,你也猜到了我要说什么,对的,复用和封装。

先看人家的源码,貌似这哥们连停止动画都没写呢,就一个无限循环。。。

 var lightLoader = function(c, cw, ch){

     var that = this;
this.c = c;
this.ctx = c.getContext('2d');
this.cw = cw;
this.ch = ch;
this.raf = null; this.loaded = 0;
this.loaderSpeed = .6;
this.loaderWidth = cw * 0.8;
this.loaderHeight = 20;
this.loader = {
x: (this.cw/2) - (this.loaderWidth/2),
y: (this.ch/2) - (this.loaderHeight/2)
};
this.particles = [];
this.particleLift = 220;
this.hueStart = 0
this.hueEnd = 120;
this.hue = 0;
this.gravity = .15;
this.particleRate = 4; /*========================================================*/
/* Initialize
/*========================================================*/
this.init = function(){
this.loaded = 0;
this.particles = [];
this.loop();
}; /*========================================================*/
/* Utility Functions
/*========================================================*/
this.rand = function(rMi, rMa){return ~~((Math.random()*(rMa-rMi+1))+rMi);};
this.hitTest = function(x1, y1, w1, h1, x2, y2, w2, h2){return !(x1 + w1 < x2 || x2 + w2 < x1 || y1 + h1 < y2 || y2 + h2 < y1);}; /*========================================================*/
/* Update Loader
/*========================================================*/
this.updateLoader = function(){
if(this.loaded < 100){
this.loaded += this.loaderSpeed;
} else {
this.loaded = 0;
}
}; /*========================================================*/
/* Render Loader
/*========================================================*/
this.renderLoader = function(){
this.ctx.fillStyle = '#000';
this.ctx.fillRect(this.loader.x, this.loader.y, this.loaderWidth, this.loaderHeight); this.hue = this.hueStart + (this.loaded/100)*(this.hueEnd - this.hueStart); var newWidth = (this.loaded/100)*this.loaderWidth;
this.ctx.fillStyle = 'hsla('+this.hue+', 100%, 40%, 1)';
this.ctx.fillRect(this.loader.x, this.loader.y, newWidth, this.loaderHeight); this.ctx.fillStyle = '#222';
this.ctx.fillRect(this.loader.x, this.loader.y, newWidth, this.loaderHeight/2);
}; /*========================================================*/
/* Particles
/*========================================================*/
this.Particle = function(){
this.x = that.loader.x + ((that.loaded/100)*that.loaderWidth) - that.rand(0, 1);
this.y = that.ch/2 + that.rand(0,that.loaderHeight)-that.loaderHeight/2;
this.vx = (that.rand(0,4)-2)/100;
this.vy = (that.rand(0,that.particleLift)-that.particleLift*2)/100;
this.width = that.rand(2,6)/2;
this.height = that.rand(2,6)/2;
this.hue = that.hue;
}; this.Particle.prototype.update = function(i){
this.vx += (that.rand(0,6)-3)/100;
this.vy += that.gravity;
this.x += this.vx;
this.y += this.vy; if(this.y > that.ch){
that.particles.splice(i, 1);
}
}; this.Particle.prototype.render = function(){
that.ctx.fillStyle = 'hsla('+this.hue+', 100%, '+that.rand(50,70)+'%, '+that.rand(20,100)/100+')';
that.ctx.fillRect(this.x, this.y, this.width, this.height);
}; this.createParticles = function(){
var i = this.particleRate;
while(i--){
this.particles.push(new this.Particle());
};
}; this.updateParticles = function(){
var i = this.particles.length;
while(i--){
var p = this.particles[i];
p.update(i);
};
}; this.renderParticles = function(){
var i = this.particles.length;
while(i--){
var p = this.particles[i];
p.render();
};
}; /*========================================================*/
/* Clear Canvas
/*========================================================*/
this.clearCanvas = function(){
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.clearRect(0,0,this.cw,this.ch);
this.ctx.globalCompositeOperation = 'lighter';
}; /*========================================================*/
/* Animation Loop
/*========================================================*/
this.loop = function(){
var loopIt = function(){
that.raf = requestAnimationFrame(loopIt);
that.clearCanvas(); that.createParticles(); that.updateLoader();
that.updateParticles(); that.renderLoader();
that.renderParticles(); };
loopIt();
}; this.stop = function(){
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.clearRect(0,0,this.cw,this.ch);
window.cancelAnimationFrame(this.raf);
} }; /*========================================================*/
/* Setup requestAnimationFrame when it is unavailable.
/*========================================================*/
var setupRAF = function(){
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x){
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}; if(!window.requestAnimationFrame){
window.requestAnimationFrame = function(callback, element){
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}; if (!window.cancelAnimationFrame){
window.cancelAnimationFrame = function(id){
clearTimeout(id);
};
};
};

我在源码基础上加了个stop,初始化的时候清除了进度条位置和粒子位置,改动就这两点。大家可以在gitHub上fork我改动过后的版本:https://github.com/QieGuo2016/Light-Loader

引用这个组件也非常简单:

var c = document.createElement('canvas');
c.width = 400;
c.height = 100;
c.id = 'canvas-test1';
parent.appendChild(c); //在需要的位置加入canvas元素
var loader = new lightLoader(c,c.width,c.height);
setupRAF(); //不支持requestAnimationFrame浏览器的替代方案
loader.init();

读源码

这个源码写的也比较规范,结构清晰、组件和DOM分离得很好,是个学习的好题材!下面说说我对源码的理解,菜鸟一枚,有错请务必指出!

(一) 构造函数的形参

像进度条这样的小组件,我们应该尽量将其封装到一个全局变量中,如:var lightLoader = function(e) { }; 。源码中传入的参数是一个canvas和宽高,但是假如我们要设置进度条的属性的时候,就必须到源码里面去改动了,这样的话可复用性就打了个打折扣。还好,与进度条相关的属性都被封装到了全局变量的属性中,要改动的话实例化后直接改lightLoade.属性也可以使用。

如果要增加组件的自由度,可以使用一个对象作为形参:var lightLoader = function(opt) { };

设置传入一个对象的话,后续要对这个组件进行扩展或者改动的时候,那对象参数的便利性就体现得淋漓尽致了。

比如我要扩展一个进度条的宽度:this.loaderHeight = opt.loaderHeight ? opt.loaderHeight : 20; 就完事了(实参的类型和值的安全性暂不讨论哈!)。原来的var lightLoader = function(c, cw, ch){} 如果要扩展一个进度条的宽度,想当然地我们可以写出 var lightLoader = function(c, cw, ch, lw) { this.loaderHeight = lw ? lw : 20 },但是麻烦的是,当我们new lightLoader(c, 20)的时候,20并没有传到给宽度啊。因为参数是有顺序的,而对象的属性则安全得多。

(二) 定义对象的方式

源码里面定义lightLoader时使用的是经典的构造函数的方式,将属性和函数都放在构造函数中,而粒子Particle的方法则是放在Particle的原型中定义的。这很关键!

经典构造函数带来的问题可以自行百度,博客园上介绍也非常多,一搜一百页。简单来说就是构造函数内部的所有函数和属性都会被复制到每个实例中,比如说我通过构造函数创建了5个实例,那在内存中就有5份副本存在。但是很明显,方法(不习惯说函数。。。)不应该被复制5份,而应该是5个实例共享一个方法即可。所以,目前推荐的是使用混合模式定义对象:属性放在构造函数中,方法放在原型中。对于数量较大(比如说本例中的粒子),那方法甚至属性都应该放在原型中,以减少内存消耗,提高动画流畅度。

虽然源码那样写了, 但是我还是觉得lightLoader对象的方法也应该放到原型中,这是也是个代码规范的问题。

(三)封装问题

源码中所有属性都被定义为this.**,也就是说都暴露给外界了。这些属性都是跟效果相关的,很多属性需要看着效果调试出来的。暴露出来的好处就是调试的时候可以在运行时动态改变相应的值,观察效果的变化,非常方便。你们感受一下:

但并不是所有属性都应该被暴露出来的,哪些需要暴露,哪些需要隐藏这个要看具体场景了。另外私有成员的命名潜规则(←.←)是前面加_,私有属性和私有方法都应该这样命名,这样同类们一看到就懂啦。

封装的另外一个方面是要与DOM对象松耦合,一个组件假如跟其他元素的联系很紧密的话,移植性就非常差了。这一点暂时我还没太多体会,不敢妄议。

就说到这里啦,看起来不是很有料呢。。。所以,还是补张图片丰满一下吧~码字不易,顺手点赞哈!

(图片出处:著名摄影师 小张同学,转载请注明)

原创文章,转载请注明出处!本文链接:http://www.cnblogs.com/qieguo/p/5438380.html 

超炫的HTML5粒子效果进度条 VS 如何规范而优雅地code的更多相关文章

  1. 简直要逆天!超炫的 HTML5 粒子效果进度条

    我喜欢粒子效果作品,特别是那些能够应用于实际的,例如这个由 Jack Rugile 基于 HTML5 Cavnas 编写的进度条效果.看着这么炫的 Loading 效果,即使让我多等一会也无妨:)你呢 ...

  2. Planetary.js:帮助你构建超炫的互动球体效果

    Planetary.js 是一个 JavaScript 库,用于构建互动球体效果.它使用 D3 和 TopoJSON 解析和渲染地理数据.Planetary.js 采用了基于插件的架构,即使是默认的功 ...

  3. 程序猿必备的10款超炫酷HTML5 Canvas插件

    1.超炫酷HTML5 Canvas 3D旋转地球动画 这是一款基于HTML5 Canvas的3D地球模拟动画,动画以太空作为背景,地球在太空中旋转,同时我们也可以拖拽鼠标来从不同的角度观察地球.另外我 ...

  4. HTML5有特色的进度条

    查看效果:http://keleyi.com/keleyi/phtml/html5/26.htm 完整代码如下: <!DOCTYPE html> <html> <head ...

  5. ScrollReveal.js – 帮助你实现超炫的元素运动效果

    ScrollReveal.js 用于创建和管理元素进入可视区域时的动画效果,帮助你的网站增加吸引力.只需要给元素增加 data-scrollreveal 属性,当元素进入可视区域的时候会自动被触发设置 ...

  6. html5 svg 圆形进度条

    html5 svg 圆形进度条 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

  7. 【转】15个超炫的HTML5效果

    英文原文:http://www.hongkiat.com/blog/15-html5-experiments/     翻译:iteye 乔布斯没有给Flash任何机会,微软新推出的Windows 8 ...

  8. iOS动画开发之五——炫酷的粒子效果

    在上几篇博客中,我们对UIView层的动画以及iOS的核心动画做了介绍,基本已经可以满足iOS应用项目中所有的动画需求,如果你觉得那些都还不够炫酷,亦或是你灵光一现,想用UIKit框架写出一款炫酷的休 ...

  9. Qt实现炫酷启动图-动态进度条

    目录 一.简述 二.动效进度条 1.光效进度条 2.延迟到达进度条 3.接口说明 三.启动图 1.实现思路 2.背景图切换 四.测试 1.构造启动图 2.背景图 3.其他信息 4.事件循环 五.源码 ...

随机推荐

  1. ExtJS 4.2 评分组件

    上一文章是扩展ExtJS自带的Date组件.在这里将创建一个评分组件. 目录 1. 介绍 2. 示例 3. 资源下载 1. 介绍 代码参考的是 Sencha Touch 2上的一个RatingStar ...

  2. 【每日一linux命令4】常用参数:

     下面所列的是常见的参数(选项)义: --help,-h                              显示帮助信息 --version,-V                        ...

  3. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  4. “四核”驱动的“三维”导航 -- 淘宝新UI(需求分析篇)

    前言 孔子说:"软件是对客观世界的抽象". 首先声明,这里的"三维导航"和地图没一毛钱关系,"四核驱动"和硬件也没关系,而是为了复杂的应用而 ...

  5. gulp详细入门教程

    本文链接:http://www.ydcss.com/archives/18 gulp详细入门教程 简介: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优 ...

  6. 【初学python】使用python连接mysql数据查询结果并显示

    因为测试工作经常需要与后台数据库进行数据比较和统计,所以采用python编写连接数据库脚本方便测试,提高工作效率,脚本如下(python连接mysql需要引入第三方库MySQLdb,百度下载安装) # ...

  7. 从零开始编写自己的C#框架(27)——什么是开发框架

    前言 做为一个程序员,在开发的过程中会发现,有框架同无框架,做起事来是完全不同的概念,关系到开发的效率.程序的健壮.性能.团队协作.后续功能维护.扩展......等方方面面的事情.很多朋友在学习搭建自 ...

  8. 更愉快的书写CSS

    我在写CSS的时候经常会碰到些麻烦事儿: 1)看上去蛮简单的排版却写了很久 2)代码写的越来越散,总是这里补一句,那里补一句,没有条理性 3)margin.padding.font-size等属性在不 ...

  9. ReactiveCocoa代码实践之-UI组件的RAC信号操作

    上一节是自己对网络层的一些重构,本节是自己一些代码小实践做出的一些demo程序,基本涵盖大多数UI控件操作. 一.用UISlider实现调色板 假设我们现在做一个demo,上面有一个View用来展示颜 ...

  10. 使用gulp解决RequireJS项目前端缓存问题(二)

    1.前言 这一节,我们主要解决在上一节<使用gulp解决RequireJSs项目前端缓存问题(一)>末尾提到的几个问题: 对通过require-config.js引入的js文件修改后,没有 ...