/* このファイルを を使ってHTML内に置くと、 "whiteareaID"というIDを付けたHTML要素の上部にCANVASが表示され、 自動的に動作を開始します。 (最初のH2タグの位置までをCANVASの縦サイズとしています) 以下は、ある程度コメントでプログラムの案内をしています。 */ //オブジェクトに関数を書く様式でプログラムを書いています。 app = { uid : Date.now(), //uidと言ってもデバッグプリントやHTML要素のIDとして使っているだけのものです //ページ読み込み完了時の関数 onload : function( e ) { console.log( this.uid, "onload" ); //CANVAS作成など this.timerID = null; this.timerMS = 100; var canvasElement = document.createElement( "canvas" ); canvasElement.id = "canvas" + this.uid; this.p = document.getElementById( "whiteareaID" ); this.p.appendChild( canvasElement ); this.cc = document.getElementById( canvasElement.id ).getContext( "2d" ); //ccはCanvasContextの略です //---vars1 //varsの中に星やカメラなどアプリケーションの動きに関する //変数を置いています。「なんかいいことあるかな」と思って。 this.vars = { stars : new Array(), maxStars : 1000, colors : [ "blue", "red", "magenta", "lightgreen", "cyan", "yellow", "white" ], //カメラ cam : { s : 100, //画角 rotationX : 0, rotationY : 0, rotationZ : 0, }, counter : 0, times : 0, step : 100, info : false, message : "テスト", } //画面の上のほうクリックで情報表示 var app = this; this.cc.canvas.onclick = function() { app.vars.info = !app.vars.info; //トグル } this.pixel = 1; //2とかにするとファミコンみたいな画質になります。 if( 1 ) { this.cc.canvas.style.imageRendering = "pixelated"; this.cc.canvas.style.imageRendering = "optimizeSpeed"; } this.onresize(); //星を1000個作成(立方体内のランダムな位置に配置) for( var i = 0; i < this.vars.maxStars; i++ ) { var star = new Star( this ); star.x = Math.random() * this.vars.lenX + this.vars.minX; star.y = Math.random() * this.vars.lenY + this.vars.minY; star.z = Math.random() * this.vars.lenZ + this.vars.minZ; this.vars.stars.push( star ); } addEventListener( "resize", app.onresize.bind( app ), false ); addEventListener( "scroll", app.onscroll.bind( app ), false ); //このbindというのは、その関数(たとえばonresize)実行時、 //その関数内でthisにあたるオブジェクトを //引数で指定したオブジェクトに変更するというものです。 //これをやらないとthisはwindowになります。 },//onload() //ウィンドウリサイズ時の関数 onresize : function( e ) { var canvasElement = this.cc.canvas; var h2; //homepage6047のページの最初のH2タグ(日記日付を書いているタグ)を検索 for( var i = 0; i < this.p.children.length; i++ ) { var child = this.p.children[ i ]; if( child.tagName == "H2" ) { h2 = child; break; } } //そのH2タグの位置までをCANVASの縦サイズとする var pr = this.p.getBoundingClientRect(); var w = pr.width; var h = h2.offsetTop; with( canvasElement.style ) { position = "absolute"; left = "0px"; top = "0px"; width = w + "px"; height = h + "px"; zIndex = -2; backgroundColor = "black"; } //cssのwidthはそのままに、canvasのwidthをpixelで割った小さいサイズに //するので画質が落ちます。 this.cc.canvas.width = w / this.pixel; this.cc.canvas.height = h / this.pixel; //---vars2 //星が画面外に位置するなら描画しないとかそういうのに使う変数 this.vars.maxH = this.cc.canvas.width / 2; this.vars.minH = -this.cc.canvas.width / 2; this.vars.maxV = this.cc.canvas.height / 2; this.vars.minV = -this.cc.canvas.height / 2; //星が3Dの立方体の外に位置するなら削除するとかそういうのに使う変数 this.vars.len = 4000; //←立方体の辺のサイズ this.vars.lenX = this.vars.len; this.vars.lenY = this.vars.len; this.vars.lenZ = this.vars.len; this.vars.maxX = this.vars.len / 2; this.vars.minX = -this.vars.len / 2; this.vars.maxY = this.vars.len / 2; this.vars.minY = -this.vars.len / 2; this.vars.maxZ = this.vars.len / 2; this.vars.minZ = -this.vars.len / 2; this.draw( this.cc ); this.onscroll(); //このonscroll関数の中にsetInterval()があり、アニメ開始となる。 },//onresize() //---draw //画面描画 draw : function( cc ) { cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height ); cc.save(); cc.translate( cc.canvas.width / 2, cc.canvas.height /2 ); //各星について for( var i = 0; i < this.vars.stars.length; i++ ) { var star = this.vars.stars[ i ]; //check. 描画範囲外 var isOver = star.cz <= 0 || star.h < this.vars.minH || star.h > this.vars.maxH || star.v < this.vars.minV || star.v > this.vars.maxV; if( isOver ) continue; star.draw( cc ); } cc.restore(); //字幕 cc.font = "32px ''"; cc.fillStyle = "white"; var x = ( cc.canvas.width - cc.measureText( this.vars.message ).width ) / 2; cc.fillText( this.vars.message, x, cc.canvas.height - 8 ); //画面上部 情報表示 if( this.vars.info ) { var fs = 12; var ly = 1; //lx,lyのLはLocateのL var lx = 0; var tab = 130; cc.font = fs + "px ''"; cc.fillStyle = "cyan"; cc.fillText( "num of stars: " + this.vars.stars.length, lx * tab, ly++ * fs ); cc.fillText( "time: " + this.vars.times / 1000, lx * tab, ly++ * fs ); cc.fillText( "anm seek: " + this.anmseek, lx * tab, ly++ * fs ); lx ++; ly = 1; cc.fillText( "rotationX: " + Math.round( this.vars.cam.rotationX * 1000 ) / 1000, lx * tab, ly++ * fs ); cc.fillText( "rotationY: " + Math.round( this.vars.cam.rotationY * 1000 ) / 1000, lx * tab, ly++ * fs ); cc.fillText( "rotationZ: " + Math.round( this.vars.cam.rotationZ * 1000 ) / 1000, lx * tab, ly++ * fs ); lx ++; ly = 1; cc.fillText( "speed: " + this.vars.step, lx * tab, ly++ * fs ); } },//draw() //---onscroll //ウィンドウがスクロールされたときの関数 onscroll : function( e ) { //画面外ならスクリプトを停止する。画面内ならスクリプトを開始する。 //そうしないと常にパソコンのCPUを使い続ける(熱を出し続ける)ので… var rect = this.cc.canvas.getBoundingClientRect(); var canvasTop = rect.top; var canvasBottom = rect.top + rect.height; var windowBottom = window.innerHeight; var overTheTop = canvasBottom - 200 < 0; var overTheBottom = canvasTop + 200 > windowBottom; var isVisible = !overTheTop && !overTheBottom; if( this.timerID && !isVisible ) this.stop(); else if( !this.timerID && isVisible ) this.start(); }, //この setInterval() でアニメ開始となる start : function() { console.log( this.uid, "start" ); this.timerID = setInterval( this.frame.bind( this ), this.timerMS ); // this.frame(); }, //アニメ停止 stop : function() { console.log( this.uid, "stop" ); clearInterval( this.timerID ); this.timerID = 0; }, //---anms //カメラの回転などのアニメのシーケンス(いろいろなアニメを順に切り替えていく) //ちょっと変わった様式でアニメの処理を書いています… //もっとわかりやすいアニメの記述方法はないかなぁと模索して。 anmseek : 0, anms : [ { //0:時間まで待つ times : 0, frame : function( app ) { app.vars.message = "イースみたいな星空"; if( ( this.times += app.timerMS ) >= 5000 ) { this.times = 0; app.anmseek++; //この++を行うと下記のアニメへと移ります。 } }, }, { //1:減速する frame : function( app ) { app.vars.step --; if( app.vars.step < 50 ) { app.vars.message = "でも、止まっ… "; } if( app.vars.step == 0 ) { app.vars.message = "でも、止まっ…た"; app.anmseek++; } } }, { //2:時間まで待つ times : 0, frame : function( app ) { if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //3:180度方向転換する endValue : 0, frame : function( app ) { app.vars.message = "ぐるっと回転しまして…"; //check. endValueの初期設定 if( this.endValue == 0 ) { this.endValue = app.vars.cam.rotationY + Math.PI; } value = 0.05; app.vars.cam.rotationY += value; //check. 回転完了 if( app.vars.cam.rotationY >= this.endValue ) { app.vars.cam.rotationY = this.endValue; app.anmseek++; this.endValue = 0; } }, }, { //4:時間まで待つ times : 0, frame : function( app ) { if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //5:加速する frame : function( app ) { app.vars.message = "リバース"; app.vars.step ++; if( app.vars.step == 100 ) { app.anmseek++; } } }, { //6:90度方向転換する endValue : 0, frame : function( app ) { app.vars.message = "そのまま、ぐるっと回転しまして…"; //check. if( this.endValue == 0 ) { this.endValue = app.vars.cam.rotationY + Math.PI / 2; } value = 0.05; app.vars.cam.rotationY += value; //check. if( app.vars.cam.rotationY >= this.endValue ) { app.vars.cam.rotationY = this.endValue; app.anmseek++; this.endValue = 0; } }, }, { //4:時間まで待つ times : 0, frame : function( app ) { app.vars.message = "横に流れています…"; if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //5:減速する frame : function( app ) { app.vars.step --; if( app.vars.step == 0 ) { app.anmseek++; } } }, { //4:時間まで待つ times : 0, frame : function( app ) { app.vars.message = ""; if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //6:360度回転する endValue : 0, frame : function( app ) { app.vars.message = "ぐるぐるぐる"; //check. if( this.endValue == 0 ) { this.endValue = app.vars.cam.rotationZ + Math.PI * 2; } value = 0.05; app.vars.cam.rotationZ += value; //check. if( app.vars.cam.rotationZ >= this.endValue ) { app.vars.cam.rotationZ = 0; app.anmseek++; this.endValue = 0; } }, }, { //4:時間まで待つ times : 0, frame : function( app ) { app.vars.message = ""; if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //6:90度方向転換する endValue : 0, frame : function( app ) { app.vars.message = "ぐるっと回転しまして…"; //check. if( this.endValue == 0 ) { this.endValue = app.vars.cam.rotationY + Math.PI / 2; } value = 0.05; app.vars.cam.rotationY += value; //check. if( app.vars.cam.rotationY >= this.endValue ) { app.vars.cam.rotationY = 0; app.anmseek++; this.endValue = 0; } }, }, { //4:時間まで待つ times : 0, frame : function( app ) { if( ( this.times += app.timerMS ) >= 2000 ) { this.times = 0; app.anmseek++; } }, }, { //5:加速する frame : function( app ) { app.vars.message = "くりかえし"; app.vars.step ++; if( app.vars.step > 50 ) { app.vars.message = "(ちなみに上のほうクリックでデータ表示)"; } if( app.vars.step == 100 ) { app.anmseek++; } } }, ],//anms[] //setInterval()から呼ばれる関数(映像1フレーム分の処理) frame : function() { //アニメシーケンス this.vars.times = this.timerMS * this.vars.counter; var times = this.vars.times; //別名定義 this.anms[ this.anmseek ].frame( this ); //現在のアニメの1フレームを実行 //check. アニメがすべて終了した if( this.anmseek == this.anms.length ) { this.anmseek = 0; this.vars.counter = 0; this.vars.times = 0; } //星の追加 (画面外に出て削除された分、ここで追加) for( var i = this.vars.stars.length; i < this.vars.maxStars; i++ ) { var star = new Star( this ); star.x = Math.random() * this.vars.lenX + this.vars.minX; star.y = Math.random() * this.vars.lenY + this.vars.minY; star.z = this.vars.maxZ; this.vars.stars.push( star ); } //星の処理 (星ごとに1フレーム分の処理を実行) for( var i = 0; i < this.vars.stars.length; i++ ) { var star = this.vars.stars[ i ]; star.frame(); //check. 画面外で星の削除 if( star.z < this.vars.minZ ) { this.vars.stars.splice( i, 1 ); i--; continue; } //---星の3D計算 star.cx = star.x; //cx,cy,czのcはcalcのc star.cy = star.y; //calcは、3Dで回転など適用した後のx,y,z座標という意味 star.cz = star.z; //y軸回転 var k = kaiten( star.cx, star.cz, this.vars.cam.rotationY ); star.cx = k.X; star.cz = k.Y; //z軸回転 var k = kaiten( star.cx, star.cy, this.vars.cam.rotationZ ); star.cx = k.X; star.cy = k.Y; //この2つの式で3D座標から2D座標へ変換 star.h = star.cx * ( this.vars.cam.s / star.cz ); star.v = star.cy * ( this.vars.cam.s / star.cz ); //そのままだと小さい画面なので引き伸ばし star.h *= 5.25; star.v *= 5.25; }//for this.draw( this.cc ); this.vars.counter ++; },//frame() };//app function kaiten( x, y, theta2 ) { //高校で習うsin,cosです。 var hankei = Math.sqrt( x * x + y * y ); //三平方の定理でx,yから半径を得る var theta1 = Math.atan2( y, x ); //x,yから回転前の角度を得る var kaitenX = Math.cos( theta1 + theta2 ) * hankei; var kaitenY = Math.sin( theta1 + theta2 ) * hankei; return { X : kaitenX, Y : kaitenY, }; } function Star( app ) { //星のクラス this.app = app; this.x = 0; this.y = 0; this.z = 0; this.cx = 0; this.cy = 0; this.cz = 0; this.color = this.app.vars.colors[ Math.floor( Math.random() * this.app.vars.colors.length ) ]; } Star.prototype.frame = function() { //星の1フレーム分の処理 var len = this.app.vars.step; this.z -= len; } Star.prototype.draw = function( cc ) { //星の描画 cc.fillStyle = this.color; cc.fillRect( this.h, this.v, 2, 2 ); } //ページ読み込み完了で、onload関数を実行する addEventListener( "load", app.onload.bind( app ), false ); //The End of the Script.