脱初心者を目指すjavascript

脱初心者を目指すjavascript

javascript初心者のボクが脱初心者を目指すブログです。
頑張ります!

広告で遊ぼう!キャンペーン実施中


アメブロを書いている皆さん、
トップ記事の広告が中途半端な大きさの画像広告になってガッカリしてる方もいらっしゃるでしょう。
当ブログでは、そんなブロガーさんたちに最適なソリューションを提案しております。

詳しくは、こちら!

重力に注意!


最近、ブログに重力が働いているようです。
ご注意ください。
解除するボタンがどこかにあったような・・・?
Amebaでブログを始めよう!
前回の記事では単純な計算式で樹を描きました。
今回はその樹が出来る過程をアニメーションで表現してみます。

コードは以下のとおり。
次回から中身を少しずつ解説していきます。

方針としては、全ての直線をオブジェクトとして扱い、それぞれにパラメータを持たせ、フレームごとにパラメータに変更を加えて、動いているように見せる、という感じです。


(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);

}());
実行



※少し処理が重いので要注意!
javascriptの描画APIであるcanvas。
かなり原始的な描画APIのみの実装で、現状ではflashの代わりになるというものではありませんが・・・。
今回は簡単な絵を描いて遊んでみます。

今回描いてみるのは、いろんな描画API系のチュートリアルでありがちな『樹』です。
枝の終点から再帰的に関数を呼び出してさらに枝を増やし、最終的に大きな樹に見えてしまうという感じのアレです。

ではさっそく描いてみましょう。

(function(w,d){
var canvas = d.getElementById('canvas')
,ctx = canvas.getContext('2d')
,DEPTH = 12 /*何回再帰させるか*/
,ROOTLENGTH = 60 /*幹の長さ*/
,ROOTWIDTH = 10; /*幹の太さ*/
ctx.clearRect(0, 0, canvas.width, canvas.height);
var drawtree = function(ctx,x,y,length,angle,depth,branchWidth){
var rnd = Math.random /*Math.random関数をキャッシュ*/
,maxBranch = 3 /*最大何本の枝に分かれるか*/
,maxAngle = Math.PI/2; /*枝の曲がる角度の最大値*/

/*第5引数で与えられた角度の向きに第4引数で与えられた長さの線を引く*/
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineCap = 'round';
ctx.lineWidth = branchWidth;
ctx.lineTo(x = x + length * Math.cos(angle),y = y + length * Math.sin(angle));

/*再帰残り2回になると色を葉っぽく緑にする。それまでは茶。*/
if(depth <= 2){
ctx.strokeStyle = 'rgb(0,'+ (((rnd() * 64) + 128) >> 0) + ',0)';
}else{
ctx.strokeStyle = 'rgb('+ (((rnd() * 64) + 64) >> 0) + ',50,25)';
}

/*描画*/
ctx.stroke();

/*残りの再帰数が0でなければdrawtree関数を再び呼び出す。*/
if(--depth){
branchWidth *= 0.7; /*次の枝を細くする*/
for(var i = 0,l = (rnd() * (maxBranch -1)) + 1;i < l;i++){
drawtree(ctx,x,y,length * (0.7 + rnd() * 0.3),angle + rnd() * maxAngle - maxAngle * 0.5,depth,branchWidth);
}
}
};

/*初期値を引数にdrawtree関数を呼び出し描画スタート*/
drawtree(ctx,canvas.width/2,canvas.height,ROOTLENGTH,-Math.PI/2,DEPTH,ROOTWIDTH);
}(window,document));
実行



コードは『JavaScriptグラフィックス ―ゲーム・スマートフォン・ウェブで使う最新テクニック』152頁を参考にしました。
単純なコードでなかなか綺麗な絵が描けましたね!

ただ、このままだと面白くないので、次回は徐々に枝が伸びていくようなアニメーションでの描画に挑戦したいと思います。
先日の記事で紹介した『リテラル値を使わないHello World問題』の自分なりの回答を公開してみます。


d=false;
process.stdout.write((function Hello(){}).name+String.fromCharCode(++d*(++d)*d*d*d*d)+(function World(){}).name);

最後のprocess.stdout.writeはconsole.logでもいいみたいです。

型変換やデフォルトの戻り値で得た数値をインクリメントして文字コードから出力する、というのが全ての言語共通の解法ですが、javascriptならではの手法を使ってみました。
javascriptでは関数はオブジェクトで、nameプロパティには関数名が格納されます。
そして半角スペースだけ文字コードから呼び出し、表示することができました。

こういう問題は頭の体操や仕様を見直す良いきっかけになりますね!