
さあ今回からひな形では定番の雪降りスクリプトを扱っていきましょう。その前にちょっと予備的なことを学んでおきますね。
今までの講座でも何度も剰余演算子が出てきたと思います。
a%b・・・aをbで割った余りを求めるでしたね。これはいったい何を意味するものか?aとbがプラスの整数であるならば、a%bは0以上b未満(b-1以下)のプラスの整数になります。
今a=a+nという計算式があるとします。nもプラスの整数です。これをa=(a+n)%bとした場合、aはやはり0以上b未満の値になります。このことからa=(a+n)%bという演算ではaのとりうる範囲は0以上b未満になる・・・ああしつこいですね。
では次の図を見てください。
雪降りさせる画像(ここではペンギンですが)をdivエレメント内で表示させます。divエレメントのサブエレメントにすればいいのですが、雪降り画像であるIMGエレメントのTOPをimgtopという変数を使用し、移動(落下)距離をddとします。
imgtop=imgtop+ddとすればddだけ移動することになりますね。このままですとdivエレメントから下にどんどん落ちるばかりですから、divエレメントの下端よりもbhだけ下になったら、divエレメントの上端よりthだけ上にはみ出たい地に戻してそこからまた落下させます。この時if〜文を使用してやればできるのですが、divエレメントのHeightとthとbhをを利用して剰余計算で行う方法があります。

divエレメントは雪降り画像を配置する包括エレメント(親エレメント)であり、とりあえずid="stage"としておきますね。
雪降り画像のTOPがとりうる値の範囲を考えてみましょう。
範囲の大きさはth+stage.clientWidth+bhですね。
ところがimgtop=(imgtop+dd)%(th+stage.clientWidth+bh)とするとimgtopは0以上、th+stage.clientWidth+bh未満の範囲になってしまいます。
雪画像のtopは-th以上、stage.clientWidth+bh未満にしたいわけです。
さあ、見えてきましたね。
imgtop=(imgtop+dd)%(th+stage.clientWidth+bh)とした上で、雪画像のtop=imgtop-thとすれば、見事に解決するではありませんか。
さて、雪降りですが、前回同様document.write()メソッドで、IMGエレメントを書き出します。今回はdivエレメント内に書き出し、divエレメントの中で雪が降るようにしたいと思います。
document,write()でエレメント内にサブエレメントを書き出すには、書き出すスクリプト部分をdiv包括エレメントを構成する開始タグと終了タグの間に記述しなければなりません。
<div id="stage" style="position:absolute; left:10px;
top: 10px; background-color:black; width:600px; top:500px;; overflow:hidden">
<script>
document.write("文字列")
</script>
</div>
雪降り画像は雪の数を設定し、その数分imgエレメントを書き出します。スクリプトでIMGエレメントを書き出す場合は、直接画像ファイルを指定することはできません。
OEでは、HTMLでのIMGエレメントに書かれたsrcしか画像が付かないのです。
そこで予めHTMLでIMGエレメントを記述し、srcを設定しておきます。スクリプトで書き出すIMGエレメントのsrcは予めHTMLで記述しておいたIMGエレメントのsrcを、やはりスクリプトで参照してやらなければなりません。この時HTMLで記述したIMGエレメントが表示されないようにしておかないと、雛型には不必要なエレメントまで表示されるということになります。HTMLで記述するIMGエレメントはあくまでもsrcの参照用なのですから。従ってHTMLで記述するIMGエレメントは非表示設定します。
非表示にするにはvisibility:hiddenあるいはdisplay:noneあるいは、position:abaoluteと一緒にleft:-1000などを使用します。
<img id="img1" src="yuki.gif"
style="display:none">
<div
id="stage" style="position:absolute; left:10px; top: 10px; background-color:black;
width:600px; top:500px; overflow:hidden">
<script>
document.write("<img
src='"+img1.src+"' style='position:absolute;
left:-1000px; top:0px'>")
</script>
</div>
では雪の数をysとし、20個にして書き出し部分を書いてみます。20回書き出しを繰り返せばいいのでfor〜繰り返し構文を使用すればいいですね。
<img id="img1" src="yuki.gif"
style="display:none">
<div
id="stage" style="position:absolute; left:10px; top: 10px; background-color:black;
width:600px; top:500px">
<script>
var ys=20//雪の数
for(i=1;i<=20;i++){
document.write("<img
src='"+img1.src+"' style='position:absolute;
left:-1000px; top:0px'>")
}
</script>
</div>
次にこの書き出したIMGエレメントのLEFTやTOPを変更することで動かすわけですから、これらエレメントをオブジェクトとして識別できるようにしなければなりません。
前回の円運動で行ったようにidを付けてeval()メソッドを使用して計算させるのもいいのですが、eval("スクリプト文字列")はとても使い辛い。文字列の部分に変数の値を参照したい場合など混乱しがちになります。
もっといい方法を考えましょう。
配列オブジェクトは配列コレクションとなっています。配列オブジェクトにはオブジェクトを入れることもできるのです。
幸い書き出したIMGエレメントはstageエレメントの子エレメントになっています。ということはstage.allコレクションでエレメントオブジェクトを取得できるのです。
var imgObj=new Array()
imgObj=stage.all
たったこれだけで子エレメントがエレメントオブジェクトコレクションとして配列オブジェクトに入ります。コレクションはインデックス番号が0からつきます。雪の数がysであり、ys個のIMGエレメントが書き出されているのですから、一番後ろのエレメントはインデックス番号がys-1になります。これはimgObj[0]〜imgObj[ys-1]という配列オブジェクトとして扱うことができるようになります。
ここで問題がひとつは発生します。実は<script>〜</script>も開始タグと終了タグで囲まれたエレメントになっています。ということはこのエレメントがstageエレメントの子エレメントになるということです。書き出し部分は<script>開始タグより後にあるのですから、スクリプトエレメントはstageエレメントの一番目の子エレメントになります。従ってimgObj[0]はスクリプトエレメントを指し、二番目以降のimg[1],imgObj[2],・・・がIMGエレメントになります。IMGエレメントにアクセスするにはインデックスを1から開始とすればいいことになりますね。
<img id="img1" src="yuki.gif"
style="display:none">
<div
id="stage" style="position:absolute; left:10px; top: 10px; background-color:black;
width:600px; top:500px; overflow:hidden">
<script>
var ys=20//雪の数
for(i=1;i<=20;i++){
document.write("<img
src='"+img1.src+"' style='position:absolute;
left:-1000px; top:0px'>")
}
</script>
</div>
<script>
var
imgObj=new Array()//雪オブジェクトを入れる配列
imgObj=stage.all
</script>
これはとても便利なんですよ。なんてったって書き出すIMGエレメントにidを付けなくていい。エレメントオブジェクトはimgObj[インエックス番号]で呼び出せる。
次の段階へと進みます。
雪それぞれの何を扱うかを考えた時に、現在のTOPを入れる箱があると計算が楽ですよね。変数を使用しないでimgObj[n].style.pixelTopを直接計算で使用してもいいのですが、変数があると見やすくなりそうです。雪ひとつひとつはTOPが同じであるはずがないので(同じ場合は横一列並んで振り落ちる・・・おかしいでしょ?)、ys個分用意しなければなりません。これも配列オブジェクトを使用するといいのです。
var imgtop=new Array()//雪のTOP計算用配列
雪降りは最初に一つ目が一番上から落ちてきて、時間差で二番目が落ちてきて・・・というようにしたいと思います。雪の横方向の位置ですが、これはLEFTになりますね。まずは揺れるということは考えないで作ってみましょう。LEFTも変数を用意しますか?
var imgleft=new Array()//雪のLFT用配列
雪のLEFTはランダムになるようにしましょう。stageエレメントの横幅より左右25ピクセル狭い範囲でランダムになるように設定します。
imgleft[n]=Math.random()*(stage.clientWidth-25-25)+25・・・nは雪エレメント番号
雪は一番上(-th)から降り始めます。つまりimgtop[n]=-thが初期値になりますね。
これを雪降り開始関数として記述します。雪はys個あるので次のようになります。そうそう雪を動かす関数をyukiMove()とします。雪にはインデックス番号でアクセスするので、この関数には引数として番号を渡します。yukiMove(n)
function yukistart(){
for(i=1;i<=ys;i++){
imgtop[i]=-th
imgleft[i]=Math.random()*(stage.clientWidth-25-25)+25
imgObj[i].style.pixelLeft=imgleft[i]
imgObj[i].style.pixelTop=imgtop[i]
setTimeout("yukiMove("+i+")",yt*(i-1)*1000)
}
}
上のsetTimeout("yukiMove("+i+")",yt*(i-1)*1000)部分ですが、時間差でyukiMove()関数を実行させるためにsetTimeoutw()メソッドを使用しています。ytは雪が落ち始める間隔です。ここでは秒にしていますから1000でかけていますね。(i-1)ですが、一つ目はiが1ですから、すぐにyukiMove(1)を実行させます(yt*(1-1)*1000は0なので)。二つ目はどうなりますか?
yt*(2-1)*1000ですから、yukiMove(2)はyt*1000秒後に実行されることになりますね。
次の雪が動き始めるのはys*1000秒後、yt*1000秒間隔で動かそうというわけです。
さあ肝心のyukiMove(n)関数です。nは雪エレメントの番号受け取り用の引数ですよ。
function yukiMove(n){
imgtop[n]=(imgtop[n]+dd)%(th+stage.clientHeight+bh)
imgObj[n].style.pixelTop=imgtop[n]-th
}
最初に予備知識として説明しましたように、それぞれの雪エレメント用に用意したimgtop[n]配列変数にはdd分だけ加算してから移動縦幅で割った剰余を求めるようにしています。この値にthだけ差し引いたものをimgObj.style.pixelTopに代入してやります。
さてこのままでは繰り返ししませんので、setTimeout()で繰り返させましょう。繰り返しする時間(速度)をsk(ミリ秒)にすると、
function yukiMove(n){
imgtop[n]=(imgtop[n]+dd)%(th+stage.clientHeight+bh)
imgObj[n].style.pixelTop=imgtop[n]-th
setTimeout("yukiMove("+n+")",sk)
}
となります。
では完成させましょう。
[ソース]
<HTML>
<head>
<meta http-equiv="content-type"
content="text/html; charset=shift_jis">
<META http-equiv="Content-Style-Type"
content="text/css">
<title></title>
</head>
<body
style="overflow:auto; margin:0px; padding:0px">
<img id="img1"
src="yuki.gif" style="display:none">
<div
id="stage" style="position:absolute; left:10px; top: 10px; background-color:black;
width:600px; height:500px; overflow:hidden">
<script>
var
ys=20//雪の数
var imgtop=new Array()//雪のTOP計算用配列
var imgleft=new
Array()//雪のLFT用配列
var dd=5//落ちる重さ
var th=30//上部配置位置
var
bh=50//下部end位置
var sk=50//速度(ミリ秒)
var yt=2//雪の間隔(秒)
var
imgObj=new Array()//雪オブジェクトを入れる配列
for(i=1;i<=20;i++){
document.write("<img
src='"+img1.src+"' style='position:absolute; left:-1000px; top:0px'>")
}
imgObj=stage.all
function
yukiMove(n){
imgtop[n]=(imgtop[n]+dd)%(th+stage.clientHeight+bh)
imgObj[n].style.pixelTop=imgtop[n]-th
setTimeout("yukiMove("+n+")",sk)
}
function
yukistart(){
for(i=1;i<=ys;i++){
imgtop[i]=-th
imgleft[i]=Math.random()*(stage.clientWidth-25-25)+25
imgObj[i].style.pixelLeft=imgleft[i]
imgObj[i].style.pixelTop=imgtop[i]
setTimeout("yukiMove("+i+")",yt*(i-1)*1000)
}
}
setTimeout("yukistart()",3000)
</script>
</div>
</body>
</html>
今回は基本形としてdocument.write()でdivエレメント内にIMGエレメントを書き出しました。このためimgObjにはスクリプトエレメントもコレクション要素になってしまいました。
もしこれが嫌であれば、スクリプトをdivエレメントの外に出し、document.write()の代わりに、stage.innerHTML="書き出し文字列"とするか、stage.insertAdjacentHTML("beforeEnd","IMGエレメント")のようにしてください。
innerHTMLでの例を示しておきます。
次回はこの基本形を改造して揺れを追加します。前回の三角関数(円運動)を利用します。