/* このファイルを を使って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; canvasElement.innerHTML = ''; // canvasElement.style.backgroundColor = "RGB(0,32,96)"; this.p = document.getElementById( "whiteareaID" ); this.p.appendChild( canvasElement ); this.cc = document.getElementById( canvasElement.id ).getContext( "2d" ); //ccはCanvasContextの略です //---vars1 //varsの中に星やカメラなどアプリケーションの動きに関する //変数を置いています。「なんかいいことあるかな」と思って。 this.vars = { image : new Image(), pixelStart : 162, cnt : 29, } this.vars.image.onload = function( e ) { document.getElementById( "javascriptloading" ).innerHTML = ""; } let idx = Math.floor( Math.random() * 3 ) + 1; this.vars.image.src = "20191201-indexJS/Sheet" + idx + ".png"; this.v = new Object(); this.v.cubeMaster = { tens : [ { x : -1, y : 1, z : 0 }, { x : 1, y : 1, z : 0 }, { x : 1, y : -1, z : 0 }, { x : -1, y : -1, z : 0 }, ], mens : [ [ 0, 1, 2, 3 ], ], } this.v.cubes = new Array(); let mx = 14; let my = 5; let lenX = 200; for( y = 0; y < my; y++ ) { for( x = 0; x < mx; x++ ) { cube = new Object(); cube.master = this.v.cubeMaster; cube.x = x * lenX - ( lenX * mx - lenX ) / 2; cube.y = y * lenX - ( lenX * my - lenX ) / 2; cube.z = 600; cube.scale = 100; cube.tensC = null; cube.rotation = { x : Math.random() * 3.14, y : 0, z : 0, }; cube.rotationSpeed = Math.random() * 6.28 / 40 + 0.05; cube.color = [ "blue", "red", "magenta", "green", "cyan", "yellow" ][ Math.floor( Math.random() * 7 ) ]; this.v.cubes.push( cube ); } } this.v.cam = { x : 0, y : 0, z : -100, s : 300, zoom : 17.25, } //画面の上のほうクリックで情報表示 var app = this; this.pixel = this.vars.pixelStart; //2とかにするとファミコンみたいな画質になります。 if( 1 ) { this.cc.canvas.style.imageRendering = "pixelated"; this.cc.canvas.style.imageRendering = "optimizeSpeed"; } this.onresize(); addEventListener( "resize", app.onresize.bind( app ), false ); addEventListener( "scroll", app.onscroll.bind( app ), false ); //このbindというのは、その関数(たとえばonresize)実行時、 //その関数内でthisにあたるオブジェクトを //引数で指定したオブジェクトに変更するというものです。 //これをやらないとthisはwindowになります。 },//onload() //---onresize //ウィンドウリサイズ時の関数 onresize : function( e ) { console.log( this.uid, "onresize" ); 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; } this.cc.canvasWidth = w; this.cc.canvasHeight = h; //cssのwidthはそのままに、canvasのwidthをpixelで割った小さいサイズに //するので画質が落ちます。 this.cc.canvas.width = w / this.pixel; this.cc.canvas.height = h / this.pixel; this.cc.scale( 1 / this.pixel, 1 / this.pixel ); this.draw( this.cc ); this.onscroll(); //このonscroll関数の中にsetInterval()があり、アニメ開始となる。 },//onresize() //---draw //画面描画 draw : function( cc ) { cc.clearRect( 0, 0, cc.canvasWidth, cc.canvasHeight ); //各点を計算 for( let j = 0; j < this.v.cubes.length; j++ ) { let cube = this.v.cubes[ j ]; cube.tensC = new Array(); for( let i = 0; i < cube.master.tens.length; i++ ) { let ten = cube.master.tens[ i ]; let tenC = new Object(); tenC.x = ten.x * cube.scale; tenC.y = ten.y * cube.scale; tenC.z = ten.z * cube.scale; //自転 - x軸回転 f = function( X, Y, theta2 ) { let hankei = Math.sqrt( X * X + Y * Y ); let theta1 = Math.atan2( Y, X ); return { X : Math.cos( theta1 + theta2 ) * hankei, Y : Math.sin( theta1 + theta2 ) * hankei, } } let res = f( tenC.z, tenC.y, cube.rotation.x ); tenC.z = res.X; tenC.y = res.Y; //移動 tenC.x += cube.x; tenC.y += cube.y; tenC.z += cube.z; tenC.h = tenC.x * ( this.v.cam.s / tenC.z ); tenC.v = tenC.y * ( this.v.cam.s / tenC.z ); cube.tensC.push( tenC ); } } //描画 cc.save(); cc.globalAlpha = 0.5; cc.translate( cc.canvasWidth / 2, cc.canvasHeight / 2 ); for( let j = 0; j < this.v.cubes.length; j++ ) { let cube = this.v.cubes[ j ]; for( let i = 0; i < cube.master.mens.length; i++ ) { let men = cube.master.mens[ i ]; cc.beginPath(); for( let k = 0; k < men.length; k++ ) { let tenIdx = men[ k ]; let tenC = cube.tensC[ tenIdx ]; cc[ k == 0 ? "moveTo" : "lineTo" ]( tenC.h, tenC.v ); } cc.closePath(); cc.fillStyle = cube.color; cc.fill(); } } cc.restore(); },//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; }, //setInterval()から呼ばれる関数(映像1フレーム分の処理) frame : function() { for( let i = 0; i < this.v.cubes.length; i++ ) this.v.cubes[ i ].rotation.x += this.v.cubes[ i ].rotationSpeed; this.draw( this.cc ); if( this.pixel == 1 ) return; this.pixel = 0.2 * this.vars.cnt * this.vars.cnt + 0.8; this.vars.cnt --; //check. if( this.pixel < 1 ) { this.pixel = 1; } this.onresize(); },//frame() };//app //ページ読み込み完了で、onload関数を実行する addEventListener( "load", app.onload.bind( app ), false ); //The End of the Script.