超炫的HTML5粒子效果进度条 VS 如何规范而优雅地code
最近瞎逛的时候发现了一个超炫的粒子进度效果,有多炫呢?请擦亮眼镜!
粗略一看真的被惊艳到了,而且很实用啊有木有!这是 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的更多相关文章
- 简直要逆天!超炫的 HTML5 粒子效果进度条
我喜欢粒子效果作品,特别是那些能够应用于实际的,例如这个由 Jack Rugile 基于 HTML5 Cavnas 编写的进度条效果.看着这么炫的 Loading 效果,即使让我多等一会也无妨:)你呢 ...
- Planetary.js:帮助你构建超炫的互动球体效果
Planetary.js 是一个 JavaScript 库,用于构建互动球体效果.它使用 D3 和 TopoJSON 解析和渲染地理数据.Planetary.js 采用了基于插件的架构,即使是默认的功 ...
- 程序猿必备的10款超炫酷HTML5 Canvas插件
1.超炫酷HTML5 Canvas 3D旋转地球动画 这是一款基于HTML5 Canvas的3D地球模拟动画,动画以太空作为背景,地球在太空中旋转,同时我们也可以拖拽鼠标来从不同的角度观察地球.另外我 ...
- HTML5有特色的进度条
查看效果:http://keleyi.com/keleyi/phtml/html5/26.htm 完整代码如下: <!DOCTYPE html> <html> <head ...
- ScrollReveal.js – 帮助你实现超炫的元素运动效果
ScrollReveal.js 用于创建和管理元素进入可视区域时的动画效果,帮助你的网站增加吸引力.只需要给元素增加 data-scrollreveal 属性,当元素进入可视区域的时候会自动被触发设置 ...
- html5 svg 圆形进度条
html5 svg 圆形进度条 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...
- 【转】15个超炫的HTML5效果
英文原文:http://www.hongkiat.com/blog/15-html5-experiments/ 翻译:iteye 乔布斯没有给Flash任何机会,微软新推出的Windows 8 ...
- iOS动画开发之五——炫酷的粒子效果
在上几篇博客中,我们对UIView层的动画以及iOS的核心动画做了介绍,基本已经可以满足iOS应用项目中所有的动画需求,如果你觉得那些都还不够炫酷,亦或是你灵光一现,想用UIKit框架写出一款炫酷的休 ...
- Qt实现炫酷启动图-动态进度条
目录 一.简述 二.动效进度条 1.光效进度条 2.延迟到达进度条 3.接口说明 三.启动图 1.实现思路 2.背景图切换 四.测试 1.构造启动图 2.背景图 3.其他信息 4.事件循环 五.源码 ...
随机推荐
- 恢复SQL Server被误删除的数据(再扩展)
恢复SQL Server被误删除的数据(再扩展) 大家对本人之前的文章<恢复SQL Server被误删除的数据> 反应非常热烈,但是文章里的存储过程不能实现对备份出来的日志备份里所删数据的 ...
- 为什么 NaN 不等于自身?
NaN 即Not a Number , 不是一个数字, 那么NaN到底是什么呢? 话说在JavaScript中,有6大数据类型,分别包括string,number,boolean,undefined, ...
- HTTPS 互联网世界的安全基础
近一年公司在努力推进全站的 HTTPS 化,作为负责应用系统的我们,在配合这个趋势的过程中,顺便也就想去搞清楚 HTTP 后面的这个 S 到底是个什么含义?有什么作用?带来了哪些影响?毕竟以前也就只是 ...
- 7.让网站支持http和https的访问方式
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html#iis 怎么让网站在本地支持SSL?http://www.c ...
- JavaScript Array对象
介绍Js的Array 数组对象. 目录 1. 介绍:介绍 Array 数组对象的说明.定义方式以及属性. 2. 实例方法:介绍 Array 对象的实例方法:concat.every.filter.fo ...
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...
- EditText 基本用法
title: EditText 基本用法 tags: EditText,编辑框,输入框 --- EditText介绍: EditText 在开发中也是经常用到的控件,也是一个比较必要的组件,可以说它是 ...
- Xamarin+Prism开发详解二:Xaml文件如何简单绑定Resources资源文件内容
我们知道在UWP里面有Resources文件xxx.resx,在Android里面有String.Xml文件等.那跨平台如何统一这些类别不一的资源文件以及Xaml设计文件如何绑定这些资源?应用支持多国 ...
- warensoft unity3d 更新说明
warensoft unity3d 组件的Alpha版本已经发布了将近一年,很多网友发送了改进的Email,感谢大家的支持. Warensoft Unity3D组件将继续更新,将改进的功能如下: 1. ...
- 深入浅出JavaScript之闭包(Closure)
闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面写下我的学习笔记~ 闭包-无处不 ...