1.开篇
1.1本文目标
本次的实例是HTML5 Canvas下雨动画DEMO演示,用到的知识主要是canvas和一个js的库dat.gui,感觉这个库老猛了,有空看一下。
2.正文
2.1代码
首先是canvas绘画部分代码
下雨的绘制:
1 ctx.beginPath();2 var rain_height = Rain.height *dpr;3 for (var i = rain.length - 1; i >= 0; i--) {4 var r =rain[i];5 var real_x = r.x *dpr;6 var real_y = r.y *dpr;7 ctx.moveTo(real_x, real_y);8 ctx.lineTo(real_x - demo.wind * r.z * dpr * 1.5, real_y -
9 rain_height *r.z);10 }11 ctx.lineWidth = Rain.width *dpr;12 ctx.strokeStyle =demo.rain_color;13 ctx.stroke();
雨水溅开水花的绘制:
1 for (var i = drops.length - 1; i >= 0; i--) {2 var d =drops[i];3 var real_x = d.x * dpr -d.radius;4 var real_y = d.y * dpr -d.radius;5 ctx.drawImage(d.canvas, real_x, real_y);6 }
这里记录几个点,首先说的是绘制用到的dpr,也就是设备像素比(devicePixelRatio),从这里的代码可以看到,canvas最终绘制的宽高和位置都是要乘以dpr,那照着dpr的公式看canvas绘制的是设备像素?这里还有点不清楚,先记着。再有就是绘制溅开的水花的方法,是每一个水花的小点都是一个对象,并且存有初始化好的canvas,然后用主canvas的drawImage方法进行绘制,嗯,记着。
下面是雨水和水花对象:
雨水Rain:
1 Rain.prototype.init = function() {2 this.y = Math.random() * -100;3 this.z = Math.random() * 0.5 + 0.5;4 this.splashed = false;5 }6 Rain.prototype.recycle = function() {7 demo.rain_pool.push(this);8 }9 //recycle rain particle and create a burst of droplets
10 Rain.prototype.splash = function() {11 if (!this.splashed) {12 this.splashed = true;13 var drops =demo.drops;14 var drop_pool =demo.drop_pool;15
16 for (var i=0; i<16; i++) {17 var drop = drop_pool.pop() || newDrop();18 drops.push(drop);19 drop.init(this.x);20 }21 }22 }
水花Drop:
1 //Droplet definition
2 functionDrop() {3 this.x = 0;4 this.y = 0;5 this.radius = Math.round(Math.random() * 2 + 1) *demo.dpr;6 this.speed_x = 0;7 this.speed_y = 0;8 this.canvas = document.createElement(‘canvas‘);9 this.ctx = this.canvas.getContext(‘2d‘);10
11 //render once and cache
12 var diameter = this.radius * 2;13 this.canvas.width =diameter;14 this.canvas.height =diameter;15
16 var grd = this.ctx.createRadialGradient(this.radius, this.radius , 1, this.radius, this.radius, this.radius);17 grd.addColorStop(0, demo.rain_color);18 grd.addColorStop(1, demo.rain_color_clear);19 this.ctx.fillStyle =grd;20 this.ctx.fillRect(0, 0, diameter, diameter);21 }22 Drop.prototype.init = function(x) {23 this.x =x;24 this.y =demo.height;25 var angle = Math.random() * Math.PI - (Math.PI * 0.5);26 var speed = Math.random() *Drop.max_speed;27 this.speed_x = Math.sin(angle) *speed;28 this.speed_y = -Math.cos(angle) *speed;29 }30 Drop.prototype.recycle = function() {31 demo.drop_pool.push(this);32 }
首先说Rain对象,对象内部没有贴出来,贴出的是几个主要的方法,主要看它的回收机制recycle方法,只要飞出边界的雨水都会回收到rain_pool数组,而rain数组的是正在飞的雨水。再看Drop对象,就是主要看它的canvas设置了,还有就是init方法里面设置水平和垂直方向速度的方式,作者数学肯定很牛X。
下面是一个Ticker对象:
1 var Ticker = (function(){2 var PUBLIC_API ={};3
4 //public
5 //will call function reference repeatedly once registered, passing elapsed time and a lag multiplier as parameters
6 PUBLIC_API.addListener = functionaddListener(fn) {7 if (typeof fn !== ‘function‘) throw(‘Ticker.addListener() requires a function reference passed in.‘);8
9 listeners.push(fn);10
11 //start frame-loop lazily
12 if (!started) {13 started = true;14 queueFrame();15 }16 };17
18 //private
19 var started = false;20 var last_timestamp = 0;21 var listeners =[];22 //queue up a new frame (calls frameHandler)
23 functionqueueFrame() {24 if(window.requestAnimationFrame) {25 requestAnimationFrame(frameHandler);26 } else{27 webkitRequestAnimationFrame(frameHandler);28 }29 }30 functionframeHandler(timestamp) {31 var frame_time = timestamp -last_timestamp;32 last_timestamp =timestamp;33 //make sure negative time isn‘t reported (first frame can be whacky)
34 if (frame_time < 0) {35 frame_time = 17;36 }37 //- cap minimum framerate to 15fps[~68ms] (assuming 60fps[~17ms] as ‘normal‘)
38 else if (frame_time > 68) {39 frame_time = 68;40 }41
42 //fire custom listeners
43 for (var i = 0, len = listeners.length; i < len; i++) {44 listeners[i].call(window, frame_time, frame_time / 16.67);45 }46
47 //always queue another frame
48 queueFrame();49 }50
51 returnPUBLIC_API;52 }());
主要是看requestAnimationFrame,这是一个绘制动画的方法,有时间再去看。传入的listener就是下面说的step。
1 demo.step = function(time, lag) {2 //localize common references
3 var demo =window.demo;4 var speed =demo.speed;5 var width =demo.width;6 var height =demo.height;7 var wind =demo.wind;8 var rain =demo.rain;9 var rain_pool =demo.rain_pool;10 var drops =demo.drops;11 var drop_pool =demo.drop_pool;12
13 //multiplier for physics
14 var multiplier = speed *lag;15
16 //spawn drops
17 demo.drop_time += time *speed;18 while (demo.drop_time >demo.drop_delay) {19 demo.drop_time -=demo.drop_delay;20 var new_rain = rain_pool.pop() || newRain();21 new_rain.init();22 var wind_expand = Math.abs(height / new_rain.speed * wind); //expand spawn width as wind increases
23 var spawn_x = Math.random() * (width +wind_expand);24 if (wind > 0) spawn_x -=wind_expand;25 new_rain.x =spawn_x;26 rain.push(new_rain);27 }28
29 //rain physics
30 for (var i = rain.length - 1; i >= 0; i--) {31 var r =rain[i];32 r.y += r.speed * r.z *multiplier;33 r.x += r.z * wind *multiplier;34 //remove rain when out of view
35 if (r.y >height) {36 //if rain reached bottom of view, show a splash
37 r.splash();38 }39 //recycle rain
40 if (r.y > height + Rain.height * r.z || (wind < 0 && r.x < wind) || (wind > 0 && r.x > width +wind)) {41 r.recycle();42 rain.splice(i, 1);43 }44 }45
46 //splash drop physics
47 var drop_max_speed =Drop.max_speed;48 for (var i = drops.length - 1; i >= 0; i--) {49 var d =drops[i];50 d.x += d.speed_x *multiplier;51 d.y += d.speed_y *multiplier;52 //apply gravity - magic number 0.3 represents a faked gravity constant
53 d.speed_y += 0.3 *multiplier;54 //apply wind (but scale back the force)
55 d.speed_x += wind / 25 *multiplier;56 if (d.speed_x < -drop_max_speed) {57 d.speed_x = -drop_max_speed;58 }else if (d.speed_x >drop_max_speed) {59 d.speed_x =drop_max_speed;60 }61 //recycle
62 if (d.y > height +d.radius) {63 d.recycle();64 drops.splice(i, 1);65 }66 }67
68 demo.draw();69 }
首先是第三段的地方,作者用了一个巧妙的数学方法增加了雨水下落的范围,主要就是根据wind风速的变化来调节范围。亲自试过,如果不添加这段代码,雨水下降的范围只有屏幕中间的一小部分。第四段是设置雨水的x、y,第五段是水花的x、y。
最后是事件:
1 //handle interaction
2 demo.mouseHandler = function(evt) {3 demo.updateCursor(evt.clientX, evt.clientY);4 }5 demo.touchHandler = function(evt) {6 evt.preventDefault();7 var touch = evt.touches[0];8 demo.updateCursor(touch.clientX, touch.clientY);9 }10 demo.updateCursor = function(x, y) {11 x /= demo.width;
12 y /= demo.height;
13 var y_inverse = (1 -y);14
15 demo.drop_delay = y_inverse*y_inverse*y_inverse * 100 + 2;16 demo.wind = (x - 0.5) * 50;17 }18
19 document.addEventListener(‘mousemove‘, demo.mouseHandler);20 document.addEventListener(‘touchstart‘, demo.touchHandler);21 document.addEventListener(‘touchmove‘, demo.touchHandler);
主要看的是触屏的touch事件,evt的touches对象就是对多点触屏的保存。
总结
继续努力吧!