Skin:
[NORMAL]
[BLUE] [DOS] [LIGHT]  / コピーするための表示 / 実行
このファイル: /home/web6047/www/cgi-bin/prj/20190923-indexJS/b.js
1 /*
2 このファイルを
3 <script src="b.js"></script>
4 を使ってHTML内に置くと、
5 "whiteareaID"というIDを付けたHTML要素の上部にCANVASが表示され、
6 自動的に動作を開始します。
7 (最初のH2タグの位置までをCANVASの縦サイズとしています)
8
9 以下は、ある程度コメントでプログラムの案内をしています。
10 */
11
12
13 //オブジェクトに関数を書く様式でプログラムを書いています。
14
15 app = {
16 uid : Date.now(), //uidと言ってもデバッグプリントやHTML要素のIDとして使っているだけのものです
17
18 //ページ読み込み完了時の関数
19 onload : function( e ) {
20 console.log( this.uid, "onload" );
21
22 //CANVAS作成など
23 this.timerID = null;
24 this.timerMS = 100;
25 var canvasElement = document.createElement( "canvas" );
26 canvasElement.id = "canvas" + this.uid;
27 this.p = document.getElementById( "whiteareaID" );
28 this.p.appendChild( canvasElement );
29 this.cc = document.getElementById( canvasElement.id ).getContext( "2d" );
30 //ccはCanvasContextの略です
31
32 //---vars1
33
34 //varsの中に星やカメラなどアプリケーションの動きに関する
35 //変数を置いています。「なんかいいことあるかな」と思って。
36 this.vars = {
37 stars : new Array(),
38 maxStars : 1000,
39 colors : [ "blue", "red", "magenta", "lightgreen", "cyan", "yellow", "white" ],
40 //カメラ
41 cam : {
42 s : 100, //画角
43 rotationX : 0,
44 rotationY : 0,
45 rotationZ : 0,
46 },
47 counter : 0,
48 times : 0,
49 step : 100,
50 info : false,
51 message : "テスト",
52 }
53
54 //画面の上のほうクリックで情報表示
55 var app = this;
56 this.cc.canvas.onclick = function() {
57 app.vars.info = !app.vars.info; //トグル
58 }
59
60 this.pixel = 1; //2とかにするとファミコンみたいな画質になります。
61 if( 1 ) {
62 this.cc.canvas.style.imageRendering = "pixelated";
63 this.cc.canvas.style.imageRendering = "optimizeSpeed";
64 }
65
66 this.onresize();
67
68 //星を1000個作成(立方体内のランダムな位置に配置)
69 for( var i = 0; i < this.vars.maxStars; i++ ) {
70 var star = new Star( this );
71 star.x = Math.random() * this.vars.lenX + this.vars.minX;
72 star.y = Math.random() * this.vars.lenY + this.vars.minY;
73 star.z = Math.random() * this.vars.lenZ + this.vars.minZ;
74 this.vars.stars.push( star );
75 }
76
77 addEventListener( "resize", app.onresize.bind( app ), false );
78 addEventListener( "scroll", app.onscroll.bind( app ), false );
79 //このbindというのは、その関数(たとえばonresize)実行時、
80 //その関数内でthisにあたるオブジェクトを
81 //引数で指定したオブジェクトに変更するというものです。
82 //これをやらないとthisはwindowになります。
83
84 },//onload()
85
86 //ウィンドウリサイズ時の関数
87 onresize : function( e ) {
88 var canvasElement = this.cc.canvas;
89 var h2;
90 //homepage6047のページの最初のH2タグ(日記日付を書いているタグ)を検索
91 for( var i = 0; i < this.p.children.length; i++ ) {
92 var child = this.p.children[ i ];
93 if( child.tagName == "H2" ) {
94 h2 = child;
95 break;
96 }
97 }
98
99 //そのH2タグの位置までをCANVASの縦サイズとする
100 var pr = this.p.getBoundingClientRect();
101 var w = pr.width;
102 var h = h2.offsetTop;
103 with( canvasElement.style ) {
104 position = "absolute";
105 left = "0px";
106 top = "0px";
107 width = w + "px";
108 height = h + "px";
109 zIndex = -2;
110 backgroundColor = "black";
111 }
112 //cssのwidthはそのままに、canvasのwidthをpixelで割った小さいサイズに
113 //するので画質が落ちます。
114 this.cc.canvas.width = w / this.pixel;
115 this.cc.canvas.height = h / this.pixel;
116 //---vars2
117 //星が画面外に位置するなら描画しないとかそういうのに使う変数
118 this.vars.maxH = this.cc.canvas.width / 2;
119 this.vars.minH = -this.cc.canvas.width / 2;
120 this.vars.maxV = this.cc.canvas.height / 2;
121 this.vars.minV = -this.cc.canvas.height / 2;
122
123 //星が3Dの立方体の外に位置するなら削除するとかそういうのに使う変数
124 this.vars.len = 4000; //←立方体の辺のサイズ
125 this.vars.lenX = this.vars.len;
126 this.vars.lenY = this.vars.len;
127 this.vars.lenZ = this.vars.len;
128 this.vars.maxX = this.vars.len / 2;
129 this.vars.minX = -this.vars.len / 2;
130 this.vars.maxY = this.vars.len / 2;
131 this.vars.minY = -this.vars.len / 2;
132 this.vars.maxZ = this.vars.len / 2;
133 this.vars.minZ = -this.vars.len / 2;
134
135
136 this.draw( this.cc );
137 this.onscroll(); //このonscroll関数の中にsetInterval()があり、アニメ開始となる。
138
139 },//onresize()
140
141 //---draw
142 //画面描画
143 draw : function( cc ) {
144 cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height );
145 cc.save();
146 cc.translate( cc.canvas.width / 2, cc.canvas.height /2 );
147 //各星について
148 for( var i = 0; i < this.vars.stars.length; i++ ) {
149 var star = this.vars.stars[ i ];
150 //check. 描画範囲外
151 var isOver = star.cz <= 0
152 || star.h < this.vars.minH
153 || star.h > this.vars.maxH
154 || star.v < this.vars.minV
155 || star.v > this.vars.maxV;
156 if( isOver ) continue;
157
158 star.draw( cc );
159 }
160 cc.restore();
161
162 //字幕
163 cc.font = "32px ''";
164 cc.fillStyle = "white";
165 var x = ( cc.canvas.width - cc.measureText( this.vars.message ).width ) / 2;
166 cc.fillText( this.vars.message, x, cc.canvas.height - 8 );
167
168 //画面上部 情報表示
169 if( this.vars.info ) {
170 var fs = 12;
171 var ly = 1; //lx,lyのLはLocateのL
172 var lx = 0;
173 var tab = 130;
174 cc.font = fs + "px ''";
175 cc.fillStyle = "cyan";
176 cc.fillText( "num of stars: " + this.vars.stars.length, lx * tab, ly++ * fs );
177 cc.fillText( "time: " + this.vars.times / 1000, lx * tab, ly++ * fs );
178 cc.fillText( "anm seek: " + this.anmseek, lx * tab, ly++ * fs );
179 lx ++; ly = 1;
180 cc.fillText( "rotationX: " + Math.round( this.vars.cam.rotationX * 1000 ) / 1000, lx * tab, ly++ * fs );
181 cc.fillText( "rotationY: " + Math.round( this.vars.cam.rotationY * 1000 ) / 1000, lx * tab, ly++ * fs );
182 cc.fillText( "rotationZ: " + Math.round( this.vars.cam.rotationZ * 1000 ) / 1000, lx * tab, ly++ * fs );
183 lx ++; ly = 1;
184 cc.fillText( "speed: " + this.vars.step, lx * tab, ly++ * fs );
185 }
186 },//draw()
187
188 //---onscroll
189 //ウィンドウがスクロールされたときの関数
190 onscroll : function( e ) {
191 //画面外ならスクリプトを停止する。画面内ならスクリプトを開始する。
192 //そうしないと常にパソコンのCPUを使い続ける(熱を出し続ける)ので…
193 var rect = this.cc.canvas.getBoundingClientRect();
194 var canvasTop = rect.top;
195 var canvasBottom = rect.top + rect.height;
196 var windowBottom = window.innerHeight;
197 var overTheTop = canvasBottom - 200 < 0;
198 var overTheBottom = canvasTop + 200 > windowBottom;
199 var isVisible = !overTheTop && !overTheBottom;
200 if( this.timerID && !isVisible ) this.stop();
201 else if( !this.timerID && isVisible ) this.start();
202 },
203
204 //この setInterval() でアニメ開始となる
205 start : function() {
206 console.log( this.uid, "start" );
207 this.timerID = setInterval( this.frame.bind( this ), this.timerMS );
208 // this.frame();
209 },
210
211 //アニメ停止
212 stop : function() {
213 console.log( this.uid, "stop" );
214 clearInterval( this.timerID );
215 this.timerID = 0;
216 },
217
218 //---anms
219
220 //カメラの回転などのアニメのシーケンス(いろいろなアニメを順に切り替えていく)
221 //ちょっと変わった様式でアニメの処理を書いています…
222 //もっとわかりやすいアニメの記述方法はないかなぁと模索して。
223 anmseek : 0,
224 anms : [
225 {
226 //0:時間まで待つ
227 times : 0,
228 frame : function( app ) {
229 app.vars.message = "イースみたいな星空";
230 if( ( this.times += app.timerMS ) >= 5000 ) {
231 this.times = 0;
232 app.anmseek++; //この++を行うと下記のアニメへと移ります。
233 }
234 },
235 },
236 {
237 //1:減速する
238 frame : function( app ) {
239 app.vars.step --;
240 if( app.vars.step < 50 ) {
241 app.vars.message = "でも、止まっ… ";
242 }
243 if( app.vars.step == 0 ) {
244 app.vars.message = "でも、止まっ…た";
245 app.anmseek++;
246 }
247
248 }
249 },
250 {
251 //2:時間まで待つ
252 times : 0,
253 frame : function( app ) {
254 if( ( this.times += app.timerMS ) >= 2000 ) {
255 this.times = 0;
256 app.anmseek++;
257 }
258 },
259 },
260 {
261 //3:180度方向転換する
262 endValue : 0,
263 frame : function( app ) {
264 app.vars.message = "ぐるっと回転しまして…";
265 //check. endValueの初期設定
266 if( this.endValue == 0 ) {
267 this.endValue = app.vars.cam.rotationY + Math.PI;
268 }
269 value = 0.05;
270 app.vars.cam.rotationY += value;
271 //check. 回転完了
272 if( app.vars.cam.rotationY >= this.endValue ) {
273 app.vars.cam.rotationY = this.endValue;
274 app.anmseek++;
275 this.endValue = 0;
276 }
277 },
278 },
279 {
280 //4:時間まで待つ
281 times : 0,
282 frame : function( app ) {
283 if( ( this.times += app.timerMS ) >= 2000 ) {
284 this.times = 0;
285 app.anmseek++;
286 }
287 },
288 },
289 {
290 //5:加速する
291 frame : function( app ) {
292 app.vars.message = "リバース";
293 app.vars.step ++;
294 if( app.vars.step == 100 ) {
295 app.anmseek++;
296 }
297
298 }
299 },
300 {
301 //6:90度方向転換する
302 endValue : 0,
303 frame : function( app ) {
304 app.vars.message = "そのまま、ぐるっと回転しまして…";
305 //check.
306 if( this.endValue == 0 ) {
307 this.endValue = app.vars.cam.rotationY + Math.PI / 2;
308 }
309 value = 0.05;
310 app.vars.cam.rotationY += value;
311 //check.
312 if( app.vars.cam.rotationY >= this.endValue ) {
313 app.vars.cam.rotationY = this.endValue;
314 app.anmseek++;
315 this.endValue = 0;
316 }
317 },
318 },
319 {
320 //4:時間まで待つ
321 times : 0,
322 frame : function( app ) {
323 app.vars.message = "横に流れています…";
324 if( ( this.times += app.timerMS ) >= 2000 ) {
325 this.times = 0;
326 app.anmseek++;
327 }
328 },
329 },
330 {
331 //5:減速する
332 frame : function( app ) {
333 app.vars.step --;
334 if( app.vars.step == 0 ) {
335 app.anmseek++;
336 }
337
338 }
339 },
340 {
341 //4:時間まで待つ
342 times : 0,
343 frame : function( app ) {
344 app.vars.message = "";
345 if( ( this.times += app.timerMS ) >= 2000 ) {
346 this.times = 0;
347 app.anmseek++;
348 }
349 },
350 },
351 {
352 //6:360度回転する
353 endValue : 0,
354 frame : function( app ) {
355 app.vars.message = "ぐるぐるぐる";
356 //check.
357 if( this.endValue == 0 ) {
358 this.endValue = app.vars.cam.rotationZ + Math.PI * 2;
359 }
360 value = 0.05;
361 app.vars.cam.rotationZ += value;
362 //check.
363 if( app.vars.cam.rotationZ >= this.endValue ) {
364 app.vars.cam.rotationZ = 0;
365 app.anmseek++;
366 this.endValue = 0;
367 }
368 },
369 },
370 {
371 //4:時間まで待つ
372 times : 0,
373 frame : function( app ) {
374 app.vars.message = "";
375 if( ( this.times += app.timerMS ) >= 2000 ) {
376 this.times = 0;
377 app.anmseek++;
378 }
379 },
380 },
381 {
382 //6:90度方向転換する
383 endValue : 0,
384 frame : function( app ) {
385 app.vars.message = "ぐるっと回転しまして…";
386 //check.
387 if( this.endValue == 0 ) {
388 this.endValue = app.vars.cam.rotationY + Math.PI / 2;
389 }
390 value = 0.05;
391 app.vars.cam.rotationY += value;
392 //check.
393 if( app.vars.cam.rotationY >= this.endValue ) {
394 app.vars.cam.rotationY = 0;
395 app.anmseek++;
396 this.endValue = 0;
397 }
398 },
399 },
400 {
401 //4:時間まで待つ
402 times : 0,
403 frame : function( app ) {
404 if( ( this.times += app.timerMS ) >= 2000 ) {
405 this.times = 0;
406 app.anmseek++;
407 }
408 },
409 },
410 {
411 //5:加速する
412 frame : function( app ) {
413 app.vars.message = "くりかえし";
414 app.vars.step ++;
415 if( app.vars.step > 50 ) {
416 app.vars.message = "(ちなみに上のほうクリックでデータ表示)";
417 }
418 if( app.vars.step == 100 ) {
419 app.anmseek++;
420 }
421
422 }
423 },
424
425 ],//anms[]
426
427 //setInterval()から呼ばれる関数(映像1フレーム分の処理)
428 frame : function() {
429 //アニメシーケンス
430 this.vars.times = this.timerMS * this.vars.counter;
431 var times = this.vars.times; //別名定義
432 this.anms[ this.anmseek ].frame( this ); //現在のアニメの1フレームを実行
433 //check. アニメがすべて終了した
434 if( this.anmseek == this.anms.length ) {
435 this.anmseek = 0;
436 this.vars.counter = 0;
437 this.vars.times = 0;
438 }
439
440 //星の追加 (画面外に出て削除された分、ここで追加)
441 for( var i = this.vars.stars.length; i < this.vars.maxStars; i++ ) {
442 var star = new Star( this );
443 star.x = Math.random() * this.vars.lenX + this.vars.minX;
444 star.y = Math.random() * this.vars.lenY + this.vars.minY;
445 star.z = this.vars.maxZ;
446 this.vars.stars.push( star );
447 }
448
449 //星の処理 (星ごとに1フレーム分の処理を実行)
450 for( var i = 0; i < this.vars.stars.length; i++ ) {
451 var star = this.vars.stars[ i ];
452 star.frame();
453 //check. 画面外で星の削除
454 if( star.z < this.vars.minZ ) {
455 this.vars.stars.splice( i, 1 );
456 i--;
457 continue;
458 }
459
460 //---星の3D計算
461 star.cx = star.x; //cx,cy,czのcはcalcのc
462 star.cy = star.y; //calcは、3Dで回転など適用した後のx,y,z座標という意味
463 star.cz = star.z;
464
465 //y軸回転
466 var k = kaiten( star.cx, star.cz, this.vars.cam.rotationY );
467 star.cx = k.X;
468 star.cz = k.Y;
469
470 //z軸回転
471 var k = kaiten( star.cx, star.cy, this.vars.cam.rotationZ );
472 star.cx = k.X;
473 star.cy = k.Y;
474
475 //この2つの式で3D座標から2D座標へ変換
476 star.h = star.cx * ( this.vars.cam.s / star.cz );
477 star.v = star.cy * ( this.vars.cam.s / star.cz );
478
479 //そのままだと小さい画面なので引き伸ばし
480 star.h *= 5.25;
481 star.v *= 5.25;
482
483 }//for
484
485 this.draw( this.cc );
486
487 this.vars.counter ++;
488 },//frame()
489
490 };//app
491
492 function kaiten( x, y, theta2 ) {
493 //高校で習うsin,cosです。
494 var hankei = Math.sqrt( x * x + y * y ); //三平方の定理でx,yから半径を得る
495 var theta1 = Math.atan2( y, x ); //x,yから回転前の角度を得る
496 var kaitenX = Math.cos( theta1 + theta2 ) * hankei;
497 var kaitenY = Math.sin( theta1 + theta2 ) * hankei;
498 return {
499 X : kaitenX,
500 Y : kaitenY,
501 };
502 }
503
504 function Star( app ) {
505 //星のクラス
506 this.app = app;
507 this.x = 0;
508 this.y = 0;
509 this.z = 0;
510 this.cx = 0;
511 this.cy = 0;
512 this.cz = 0;
513 this.color = this.app.vars.colors[ Math.floor( Math.random() * this.app.vars.colors.length ) ];
514 }
515 Star.prototype.frame = function() {
516 //星の1フレーム分の処理
517 var len = this.app.vars.step;
518 this.z -= len;
519 }
520 Star.prototype.draw = function( cc ) {
521 //星の描画
522 cc.fillStyle = this.color;
523 cc.fillRect( this.h, this.v, 2, 2 );
524 }
525
526 //ページ読み込み完了で、onload関数を実行する
527 addEventListener( "load", app.onload.bind( app ), false );
528
529 //The End of the Script.