今回はその樹が出来る過程をアニメーションで表現してみます。
コードは以下のとおり。
次回から中身を少しずつ解説していきます。
方針としては、全ての直線をオブジェクトとして扱い、それぞれにパラメータを持たせ、フレームごとにパラメータに変更を加えて、動いているように見せる、という感じです。
実行
(function(){
var w = window,
d = document;
function AnimateLine(params){
this.x = params.x;
this.y = params.y;
this.endX = params.endX || this.x;
this.endY= params.endY || this.y;
this.width = params.width;
this.clr = params.clr || null;
this.lineCap = params.lineCap || null;
this.animParams = params.animParams || {};
this.animate = false;
this.stepAnim = function(){};
this.animComplete = function(){};
}
AnimateLine.prototype.update = function(ctx){
if(this.animate){
if(this.stepAnim(this.animParams)){
this.stopAnim();
this.animComplete();
}
}
if(ctx){
ctx.lineCap = this.lineCap ? this.lineCap : ctx.lineCap;
ctx.beginPath();
ctx.moveTo(this.x,this.y);
ctx.lineWidth = this.width ? this.width : ctx.lineWidth;
ctx.lineTo(this.endX,this.endY);
ctx['strokeS'+'tyle'] = this.clr ? this.clr : ctx['strokeS'+'tyle'];
ctx.stroke();
}
};
AnimateLine.prototype.startAnim = function(){
this.animate = true;
return this;
};
AnimateLine.prototype.stopAnim = function(){
this.animate = false;
return this;
};
var stretchLine = function(line,destX,destY,duration,fps){
var frames = ((duration * fps) / 1000) >> 0,
currentFrame = 1,
stepX = (destX - line.endX) / frames,
stepY = (destY - line.endY) / frames;
return function(){
if(currentFrame < frames){
line.endX += stepX;
line.endY += stepY;
currentFrame++;
return false;
}else if(currentFrame === frames){
line.endX = destX;
line.endY = destY;
return true;
}
};
};
if(w.enterFrame){
w.clearInterval(w.enterFrame);
}
var canvas = d.getElementById('canvas'),
ctx = canvas.getContext('2d'),
rnd = Math.random,
cWidth = canvas.width,
cHeight = canvas.height,
objs = [],
FPS = 20,
DEPTH = 12,
ROOTLENGTH = 60,
ROOTWIDTH = 10,
ANGLE = -Math.PI/2,
DURATION = 1500*rnd()+1500,
cacheImg = new Image(),
dummyCanvas = d.createElement('canvas'),
dummy = (function(p){p.width=cWidth;p.height=cHeight;return p.getContext('2d');}(dummyCanvas));
ctx.lineCap = dummy.lineCap = 'round';
(function(ctx,x,y,length,angle,depth,lineWidth,duration){
var maxBranch = 3,
maxAngle = Math.PI/2,
draw = arguments.callee,
params = {
x:x,
y:y,
width:lineWidth,
clr:depth <= 2 ? 'rgb(0,'+ (((rnd() * 64) + 128) >> 0) + ',0)' : 'rgb('+ (((rnd() * 64) + 64) >> 0) + ',50,25)'
},
destX = x + length * Math.cos(angle),
destY = y + length * Math.sin(angle),
line = new AnimateLine(params);
objs.push(line);
line.stepAnim = stretchLine(line,destX,destY,duration,FPS);
line.startAnim();
line.animComplete = function(){
if(--depth){
for(var i = 0,l = (rnd() * (maxBranch -1)) + 1;i < l;i++){
draw(ctx,destX,destY,length * (0.7 + rnd() * 0.3),angle + rnd() * maxAngle - maxAngle * 0.5,depth,lineWidth*0.7,1500*rnd()+1500);
}
}
}
}(ctx,cWidth/2,cHeight,ROOTLENGTH,ANGLE,DEPTH,ROOTWIDTH,DURATION));
w.enterFrame = w.setInterval(function(){
ctx.clearRect(0,0,cWidth,cHeight);
ctx.drawImage(cacheImg, 0, 0);
var newAr = [];
for(var i = 0,l = objs.length;i < l;i++){
var obj = objs[i];
obj.update(ctx);
if(obj.animate){
newAr.push(obj);
}else{
obj.update(dummy);
}
}
if(newAr.length < l){
Array.prototype.push.apply(newAr,objs.splice(l));
objs = newAr;
cacheImg.src = dummyCanvas.toDataURL();
}
if(!objs.length){
w.clearInterval(w.enterFrame);
alert('animation finished!');
return;
}
},1000/FPS);
}());
※少し処理が重いので要注意!
