更新日: 2024年12月22日


V. JavaアプレットをJavaScriptで代替(2)

3. 2015年に作成したJSプログラム

JavaとJavaScript(JS)が全く異なるプログラミング言語であることは本サイトにも書きましたし、世の中でも広く言われています。どちらもオブジェクト指向言語ですが、Javaはクラスベースなのに対し、JSはVBAと同じくプロトタイプベースです。VBAはMicrosoft Offceに出てくるもの全てをオブジェクトとしてツリー化していますが、JSはHTMLに関係するもの全てをオブジェクトとしてツリー化しています。JSはHTMLという相棒を失えば路頭に迷う気がしますが、JavaアプレットはJavaがカバーする領域のごく一部なので、アプレットに脆弱性があると言われれば「じゃあJavaアプレットは止めます」となりました。

2015年に駄待ち狐はアプレットをJSに置換えようと決めましたが、「大阪の公園めぐり」で使っていたPlayer.classとPointer.classは移植が厄介そうでした。それと2006年に道路族?公園族!というページを新たに作って、公園紹介のページはこちらからも見られるようになっていました。ちなみに「道路族?公園族!」でもPlayer.classを使いましたが、サウンドトラックはなかったのでJSへの移植は簡単でした。「大阪の公園めぐり」は暫くそのままにしていましたが、力作のアニメーションと「ランプ点灯式観光マップ」を無くすのは忍びないと思いながらもページ全体を削除することにしました。

3.1 runIndex.js

Runner.classを移植したrunIndex.jsのコードは以下の通りです。JSを動作させるためには、JSを呼出すHTMLと、HTMLコンテンツのスタイル指定を行うCSSも必要なので、index_j.phpの一部分であるrunIndex.htmlと、runIndex.cssも併せて掲載しています。それぞれのコードをコピーしたファイルを作成し、wchair.gifをダウンロードして同じフォルダに入れれば、ブラウザでrunIndex.htmlを開くだけでrunIndex.jsが実行できます。JSでアニメーションを表示する際には、windowオブジェクトのrequestAnimationFrameメソッドを使って、1フレーム分を表示する関数の中でこの関数自身を再帰的に呼出します。

コードの表示にはPrism.jsを使用しています。
prism.cssの設定は団塊爺ちゃんの備忘録を参考にしました。

<HTML>
<HEAD>
<LINK rel="stylesheet" type="text/css" href="runIndex.css">
</HEAD>
<BODY BGCOLOR="#3FB6BF">
<CENTER>
<DIV id="wrapHead">
<IMG src="wchair.gif" id="runFace">
<DIV id="runText" style="font-size: 24px;">姪っ子にベビー服を買ってあげたよ。</DIV>
</DIV>
<SCRIPT type="text/javascript" src="runIndex.js"></SCRIPT>
</CENTER>
</BODY>
</HTML>

#wrapHead{
	position: relative;
	height: 84px;
	width: 460px;
	overflow: hidden;
}
#runFace{
	position: absolute;
	top: 0px; 
}
#runText{
	position: absolute;
	height: 25px;
	width: 460px;
	top: 27px;
	margin-left: 10px;
	text-align: left;
}

const face = document.getElementById("runFace");
const text = document.getElementById("runText");
let iPos = 100;

function renderLoop() {
    if(iPos < 100) {
		face.style.left = "0px";
        text.style.left = "70px";
    } else {
		face.style.left = 490 - iPos + "px";
        text.style.left = 560 - iPos + "px";
	}
    switch(iPos++) {
		case 0: text.style.color = "#FF00FF"; break;
		case 100: text.style.color = "#FF0000"; break;
		case 165: text.style.color = "#FF9000"; break;
		case 230: text.style.color = "#FFFF00"; break;
		case 295: text.style.color = "#00FF00"; break;
		case 360: text.style.color = "#0000FF"; break;
		case 425: text.style.color = "#9000FF"; break;
		case 490: iPos = -1; break;
	}
    window.requestAnimationFrame(renderLoop);
}

renderLoop();

HTMLから呼出されたrunIndex.jsは、1~3行目でHTML上の要素をidで識別してface/textオブジェクトとし、位置インデックスiPosの初期値を設定した後、26行目でrederLoop()関数を実行します。rederLoop()はiPOSに応じて1フレーム分を表示しますが、その最後でまたrederLoop()を呼出すことでアニメーションになります。プログラムの再帰呼出しは一般的にスタックオーバーフローを引起しますが、この問題を解決した上でフレームレートに従って再帰呼出しをしてくれるのがrequestAnimationFrameメソッドです。フレームレートは多くのブラウザでディスプレイのリフレッシュレート(今は60Hzが主流)に設定されています。

3.2 pushBttn.js

Pusher.classとJumper.classはまとめてpushBttn.jsにしました。このコードも、pushBttn.htmlとpushBttn.cssを併せて掲載します。元のクラスではボタンを押したように影を付けるため、プログラムで8つの矩形を描いていました。JSでもHTMLの<canvas>要素と組合せれば同じことができるのですが、駄待ち狐がそのことを知ったのはPointer.classをJSに移植した2024年のことです。pushBttn.jsではボタンを押した時の影をGIFで用意して、ボタンに重ね書きしています。ボタンのサイズや背景色によってたくさんのGIFが必要です。サンプルコードでも4種類の影が必要ですが、これを含めたGIFのZIPをダウンロードして下さい。


<HTML>
<HEAD>
<LINK rel="stylesheet" type="text/css" href="pushBttn.css">
<SCRIPT type="text/javascript" src="pushBttn.js"></SCRIPT>
</HEAD>
<BODY BGCOLOR="#3FB6BF">
<CENTER>
<DIV id="wrapLh" style="margin-bottom: 8px;" onMouseDown="push(0, 'shadeLh.gif')" onMouseUp="push(1, 'https://tomochan.jp/index.php')">
<IMG src="Lhome.gif" id="imageBt">
</DIV>
<HR ALIGN=center WIDTH=100% SIZE=2>
<FORM type="#" name="hid_prm">
	<INPUT type="hidden" name="offset_x">
	<INPUT type="hidden" name="dname" VALUE="index">
	<INPUT type="hidden" name="dlang" VALUE="_j">
</FORM>
<DIV id="wrapMn" style="margin-bottom: 20px;" onMouseDown="jump(2)" onMouseUp="jump(3)">
	<IMG src="menu_j.gif" id="imageMn" onLoad="jump(1)">
</DIV>
</CENTER>

#wrapLh{
	position: relative;
	height: 25px;
	width: 208px;
}
#wrapGo{
	position: relative;
	height: 20px;
	width: 113px;
}
#imageBt{
	position: absolute;
	z-index: 0;
	top: 0px;
	left: 0px;
	border: 0px;
}
#shadeBt{
	position: absolute;
	z-index: 1;
	top: 0px;
	left: 0px;
	border: 0px;
}

#wrapMn{
	position: relative;
	height: 20px;
	width: 562px;
}
#imageMn{
	position: absolute;
	z-index: 0;
	top: 0px;
	left: 0px;
	border: 0px;
}
#shadeMnOrg{
	position: absolute;
	z-index: 1;
	top: 0px;
	border: 0px;
}
#shadeMnNew{
	position: absolute;
	z-index: 1;
	top: 0px;
	border: 0px;
}

#wrapMn2{
	position: relative;
	height: 20px;
	width: 573px;
}
#imageMn2{
	position: absolute;
	z-index: 0;
	top: 0px;
	left: 0px;
	border: 0px;
}
#shadeMnOrg2{
	position: absolute;
	z-index: 1;
	top: 0px;
	border: 0px;
}
#shadeMnNew2{
	position: absolute;
	z-index: 1;
	top: 0px;
	border: 0px;
}

function push(flg, prm){
    if(flg ===  0){
		var imgNew = document.createElement("img"); 
		imgNew.src = prm;
		imgNew.id = "shadeBt";
        if(prm === "shadeLh.gif"){
			document.getElementById("wrapLh").appendChild(imgNew); 
		}else{
			document.getElementById("wrapGo").appendChild(imgNew); 
		}
	}else{
		location.href = prm;
	}
}

function getMouseX_ie(){
	document.hid_prm.offset_x.value = event.offsetX;
}

function getMouseX_ff(e){
	var objLeft = document.getElementById("wrapMn").offsetLeft;
	document.hid_prm.offset_x.value = e.pageX - objLeft;
}

document.onmousemove = (document.all) ? getMouseX_ie : getMouseX_ff;
document.onmouseover = (document.all) ? getMouseX_ie : getMouseX_ff;

function putShade(x0, xd, i0, id0){
	if(document.getElementById("shadeMnNew")){
		var child = document.getElementById("shadeMnNew");
		child.parentNode.removeChild(child);
	}

    if(i0 !== 99){
		var imgNew = document.createElement("img"); 
        if(xd[i0] === 39){
			imgNew.src = "shadeMnL.gif";
        }else if(xd[i0] === 14){
			imgNew.src = "shadeMnM.gif";
        }else if(xd[i0] === 9){
			imgNew.src = "shadeMnS.gif";
		}
		imgNew.id = id0;
		imgNew.style.left = x0[i0] + "px";
		document.getElementById("wrapMn").appendChild(imgNew);
	}
}

function putShade2(x0, xd, i0, id0){
	if(document.getElementById("shadeMnNew2")){
		var child = document.getElementById("shadeMnNew2");
		child.parentNode.removeChild(child);
	}

	var imgNew2 = document.createElement("img"); 
    if(xd[i0] === 39){
		imgNew2.src = "shadeMnL.gif";
    }else if(xd[i0] === 14){
		imgNew2.src = "shadeMnM.gif";
    }else if(xd[i0] === 9){
		imgNew2.src = "shadeMnS.gif";
	}
	imgNew2.id = id0;
	imgNew2.style.left = x0[i0] + "px";
	document.getElementById("wrapMn2").appendChild(imgNew2);
}

function jump(flg){
    var bname = ["index", "whois", "growth1", "growth2", "growth3", "life1", "life6", "life11", "life16", "life21", "life26", "life31", "life36", "life41", "life46", "life51", "life52", "life53", "life54", "tomotv", "tomovideo", "tomofm", "park", "road"];
    var xd = [39, 39, 39, 14, 14, 39, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 9, 9, 9, 39, 39, 39, 39, 39];
	var x0 = new Array(xd.length);
	x0[0] = 0;
	for(i = 1; i < xd.length; i++){
		x0[i] = x0[i-1] + xd[i-1] + 2;
	}
	var i0 = 99;

    if(flg === 1 || flg === 12){
		for(i = 0; i < xd.length; i++){
            if(bname[i] === document.hid_prm.dname.value){
				i0 = i;
  			  }
  		}
        if(flg === 1){
			putShade(x0, xd, i0, "shadeMnOrg")
		}else{
			putShade2(x0, xd, i0, "shadeMnOrg2")
		}

	}else{
		var ofsX = document.hid_prm.offset_x.value;
		for(i = 0; i < xd.length; i++){
			if(ofsX >= x0[i] && ofsX <= (x0[i] + xd[i])){
				i0 = i;
			}
		}

        if(flg === 2){
			if(document.getElementById("shadeMnOrg")){
				var child = document.getElementById("shadeMnOrg");
				child.parentNode.removeChild(child);
			}
			putShade(x0, xd, i0, "shadeMnNew")

        }else if(flg === 22){
			if(document.getElementById("shadeMnOrg2")){
                var child2 = document.getElementById("shadeMnOrg2");
                child2.parentNode.removeChild(child);
			}

			putShade2(x0, xd, i0, "shadeMnNew2")

        }else if(flg === 3){
			dname0 = document.hid_prm.dname.value;
			dlang0 = document.hid_prm.dlang.value;
            if(bname[i0] === dname0){
                dlang0 = (dlang0 === "") ? "_j" : "";
            }else if(dname0 === "index"){
				bname[i0] = "count.cgi?" + bname[i0];
			}
			if(i0 == 0){
                url = "https://tomochan.jp/" + bname[i0] + dlang0  + ".php";
			}else{
                url = "https://tomochan.jp/" + bname[i0] + dlang0  + ".html";
			}
			location.href = url;
		}
	}
}

pushBttn.htmlの7~10行目がPusher.classに対応しますが、onMouseDownでpush(0, ...)を呼出し、onMouseUpでpush(1, ...)を呼出しています。一方、Jumper.classに相当する12~19行目では、onLoadでjump(1)、onMouseDowでjump(2)、onMouseUpでjump(3)を呼出しています。Pusher.classとJumper.classをまとめたと書きましたが、実はpushBttn.jsの中にある異なる関数を呼出しているだけで、プログラムとして統合されている訳ではありません。影のGIFをたくさん用意したことを含めてコーディングのエレガントさに欠けますが、両クラスはともちゃんHP全体に関るものなので、とにかく早くJS化したかったのです。

pushBttn.jsの25~26行目はブラウザがIEかそれ以外かでカーソルの座標取得コマンドが異なることに対応するためのものです。document.allプロパティは現在では非推奨になっており、IE以外のブラウザでは偽値となることを使ってブラウザがIEかどうかを判別しています。今や過去の遺物となったIEあるあるです。また、影のGIFを重ね書きする際には7行目他にあるdocument.(重ね書きされる要素).appendChild(重ね書きする要素)を使い、取除く際には31行目他にある(重ね書きした要素).parentNode.removeChild(重ね書きした要素)としています。ボタンの上に影を重ねるためにpushBttn.cssでz-indexを指定しています。

3.3 showTv.js

Projector.classを移植したshowTv.html、showTv.css、showTv.jsのコードは以下の通りです。JPEGファイルはともちゃんHP上のものを参照するので、それぞれのコードをコピーしたファイルを作成するだけで動作します。showTv.jsでもrunIndex.jsと同様にrequestAnimationFrameによるアニメーションを利用しています。スライドショーなので6行目のif文でiFrameが240の整数倍の時(フレームレートが60Hzであれば4秒ごと)に画像が変るようにしています。また、画像の切替えにはpushBttn.jsと同じくappendChildとremoveChildを使っています。


<HTML>
<HEAD>
<LINK rel="stylesheet" type="text/css" href="showTV.css">
</HEAD>
<BODY BGCOLOR="#3FB6BF">
<CENTER>
<DIV id="wrapTV">
<IMG src="https://tomochan.jp/photos/photo0.jpg" id="imageTVset">
<SCRIPT type="text/javascript" src="showTV.js"></SCRIPT>
</DIV>
</CENTER>
</BODY>
</HTML>

#wrapTV{
	position: relative;
	height: 330px;
	width: 355px;
}
#imageTVset{
	position: absolute;
	z-index: 0;
	top: 0px;
	left: 0px;
	border: 0px;
}
#idPhoto{
	position: absolute;
	z-index: 1;
	top: 20px;
	left: 20px;
	border: 0px;
}

let iFrame =  0;
let iSlide = 0;
const slide = document.getElementById("wrapTV");

function renderLoop(){
	if(iFrame++ % 240 == 0){
		iSlide = (iFrame - 1) / 240 + 1;
		if(iSlide == 13){
			iFrame = 0;
		}else{
            if(document.getElementById("idPhoto")){
                const child = document.getElementById("idPhoto");
				child.parentNode.removeChild(child);
			}
            const imgNew = document.createElement("img");
            imgNew.src = "https://tomochan.jp/photos/photo" + iSlide + ".jpg";
            imgNew.id = "idPhoto";
			slide.appendChild(imgNew);
		}
	}
    window.requestAnimationFrame(renderLoop);
}

renderLoop();

3.4 showSlides.js

道路族?公園族!を2006年に追加した時にはPlayer.classを使いましたが、これを2015年にJSに移植したのがshowSlides.jsで、動作にCSSは不要です。移植の前も後もサウンドトラックはなかったのですが、2024年に自作のBGMを入れたplaySlides.jsに置換えました。playSlides.jsは次の「4. 2024年に作成したJSプログラム」で紹介しますが、このBGMはネットが超低速だった時代に数コマで作った動画もどき「ともちゃん大笑い」のダウンロード待ち保留音でした。この曲を復活させたくてplaySlides.jsを作成しました。曲名は"Pew Summer"です。因みにともちゃんビデオの曲名は"Bay Solome Duas"、大阪の公園めぐりは"Lee Po Ping"です。


<HTML>
<BODY BGCOLOR="#3FB6BF">
<CENTER>
<P>
<DIV id="wrapSlide" style="width: 400; height: 400;">
<SCRIPT type="text/javascript" src="showSlides.js"></SCRIPT>
</DIV>
<P>
</CENTER>
</BODY>
</HTML>

let iFrame =  0;
let iSlide = 0;
const slide = document.getElementById("wrapSlide");

function renderLoop(){
	if(iFrame++ % 120 == 0){
		iSlide = (iFrame - 1) / 120 + 1;
		if(iSlide == 15){
			iFrame = 0;
		}else{
			if(document.getElementById("idSlide")){
				var child = document.getElementById("idSlide");
				child.parentNode.removeChild(child);
			}
			var imgNew = document.createElement("img"); 
            imgNew.src = "https://tomochan.jp/roadmap/T" + iSlide + ".gif";
			imgNew.id = "idSlide";
			slide.appendChild(imgNew);
		}
	}
	handle = window.requestAnimationFrame(renderLoop);
}

renderLoop();

JavaアプレットをJavaScriptで代替(3)へ続く