イマドキのWebサイトをみると、よくローディング画面が出てきてサイトが出現する演出をよく見かけます。なので、Webサイトを作るうえでは、不可欠な要素になってきています。
ですが、コーディングに慣れていないと作りたくてもどうやっていいか、わからないのではないでしょうか?僕もそうでした。プラグインを使うと楽なのですが、応用が効かなくなるので、この記事では1から作って、ローディング画面の実装をちゃんと理解することを目指しました。
実装したいページローディングエフェクト
実装したい内容は、ページが表示される前に現れるローディングのアニメーションの部分です。具体的には、下記の2つのwebページのローディングページの要素を組み合わせたイメージ
アニメーションの手順
- 最上部にある赤い線が左から右へ伸びる
- ロゴが現れる
- 赤い線とロゴが消える
- ヘッダーにあるナビゲーション以下のコンテンツが現れる
- ヘッダーにあるナビゲーションが上から現れる
実装手順
1.ヘッダーにあるナビゲーションが上から現れる
まず、これから作っていきましょう。ページが読み込まれた段階で、ナビゲーションが上からスライドダウンするようにします。
2.ヘッダーにあるナビゲーション以下のコンテンツが現れる
コンテンツが、すべて読み込まれたあとで、アニメーションにより(たとえば、フェードインとかを)表示します。
3.最上部にある線が左から右へ伸びて、ロゴが現れる
線が左から右へ伸びるのをタイマーを使って、実装する。右に伸びきって、線をフェードアウトしたあとで、ロゴをフェードインさせる。
ここまでを独立して作る。
4.「1~3」を組み合わせる。
1から3が大きな流れです。最終的に完成して見ると、上記とは少しずれてきます。
あと、EdgeとIE対応にするのに苦労するかもしれません。
実際に触りながら実装していきましょう
1.ヘッダーにあるナビゲーションが上から現れる
jQueryでは、基本的に、2つのタイミングを提供しています。
- 1)HTMLが読み込まれたタイミング。(これは、$(document).readyで囲って)
- 2)画像などがすべて読み込まれたタイミング。(これは、$(window).loadで囲えばいい)
参考 http://qiita.com/8845musign/items/88a8c693c84ba63cea1d
まずは、2)のタイミングで、ヘッダーをスライドダウンさせてみる。
jQuery CDN から jQuery 3.0 を読み込ませて、fileは下記のように実装する。ここまでのコミット。
1 2 3 4 5 6 7 |
$(function(){ $(".header").css("display","none"); }); $(window).on("load",function(){ $(".header").delay(1000).slideDown("slow"); }); |
ブラウザではこんな見た目(挙動)になるはず。
1 2 |
$(window).on("load",function(){ }); |
$(window).load()はページにあるすべての要素、例えば画像などがすべて読み込まれた段階で、{この中のscript}が走ります。
それぞれのイベントの順番は以下です。
- ページの読み込みが始まる
- HTMLの読み込みが終わる
- $(document).readyが実行
- 画像など含めすべてのコンテンツの読み込みが終わる
- $(window).loadが実行
試しに、容量の多い画像を16枚用意するとわかるかもしれません。ここまでのコミット。
これを見ると、先に下記を読み込んで、header部分が display: none;
されている(消えている)ことがわかる。
1 2 3 |
$(function(){ $(".header").css("display","none"); }); |
その後、画像が読み込まれて、ニュるっとheaderがslide downしてくる挙動になります。
つまり、全画像が読み込まれたタイミングで、1秒待って(.delay(1000)
)下記が走るのです。
1 2 3 |
$(window).on("load",function(){ $(".header").delay(1000).slideDown("slow"); }); |
ちなみに、load()はブラウザによって動作が保証されないため1.8で非推奨となり3.0で削除されました。今はon()の中に入れるのが一般的です。
jQuery | Category: Deprecated 1.8
2.ヘッダーにあるナビゲーション以下のコンテンツが現れる
次は、「コンテンツ部分が現れて、そのあとにheaderが現れる」を実装してみましょう。つまり、jsファイルにscriptを追加するだけです。
1 2 3 4 5 6 7 8 9 |
$(function(){ $(".header").css("display","none"); $(".contents").css("display","none"); }); $(window).on("load",function(){ $(".contents").fadeIn("slow"); $(".header").delay(2200).slideDown("slow"); }); |
最初は、.header
も.contents
もdisplay:none;
(非表示)の状態で。
画像が全部読み込まれたらon.load
内のscriptが走り、
$(".contents").fadeIn("slow");
が実行され、2.2秒後に.header
がslidedown
します。ここまでのコミット。
これをみると、画像が1つづつ読み込まれる様子が見えないので、画像が全部読み込まれたらon.load
内のscriptが走り、.contents
がfadeIn
してることがわかる。表示されるタイミングは、それぞれ、delayの時間で、調節します。
3.最上部にある線が左から右へ伸びて、ロゴが現れる
線を出すために、写真の読み込みを監視する必要があるのはなぜか?
webページを表示するまでのファイルの読み込み順は、大きく分けて、HTML → CSS・JS → img・iframe → webページの表示らしいです。
http://kawa.at.webry.info/200911/article_7.html
なのでまず、imgタグ(とiframeタグ)は、特別に読み込みをwatchしないといけない。なぜなら、線を出したり、消したりするタイミングの指標としたいので。
(iframeタグは、今回は関係ないですが、imgと同じように、onloadで、タイミングを捉えることができるということを知っておいて欲しかったから書きました)
1 2 3 |
$(document).ready(function(){ //何かしらの処理 }); |
もしくは、同じですが下記
1 2 3 |
$(function(){ //何かしらの処理 }); |
上記 script にある「何かしらの処理」をするタイミングはHTML(DOM)の読み込みが終わった後です。つまり、「何かしらの処理」の中は記述順によっては他のJSやCSSも読み込み、それを反映した表示をしてないし、ましてやimgやiframeは1つも表示されていないでしょう。
imgタグとiframeタグは、on.(load
のタイミングで監視できます。
参考:これ「script・css・img・iframe が読み込まれるタイミング調査」をみると
http://kawa.at.webry.info/200911/article_7.html
まず、HTMLが読み込まれて、次に、CSSとJSが読み込まれる、両者は優劣なしに、上から書いた順に読み込まれる。
head 内の script, css が読み込まれ、全ての読み込みが完了した後に body 内の script を読み込む
HTML 内の全ての scriptの読み込みを待ってから、次に img を読み込み、全ての img が終わったら、最後に iframe を読み込む。こういう順番らしい。で、imgタグの読み込みをwatchしたいので、最初にも出てきた、これを使う!
1 2 3 |
$(window).on("load",function(){ //何かしらの処理 }); |
imgタグを1枚づつ取得するscriptは
1 2 3 4 5 6 7 |
$(function(){ $("img").each(function(){ $(this).on("load",function(){ //何かしらの処理 }) }) }); |
こんな感じです。こうすると、イメージが読み込まれるタイミングをつかむことができます。あと、eachメソッドについては、これを参考にしてください。簡単に言うと、eachを使うと、1個ずつに対して順繰りに処理を行うことができるやつです。
eachメソッドの書き方
$.each(オブジェクト, 処理)
ループを回すためのオブジェクトを第一引数に、処理を第二引数に書きます。例えば配列に対してループを回したい時は以下のように書きます。
1 2 3 |
var array = [ “1”, “2”, “3”, “4”, “5” ]; $.each(array, function(){ }); |
この場合arrayの各要素に対して、function内の処理を行います。これはいわば、jQueryの繰り返し文です。jQueryで取得した要素の数って何個かわからない場合もあるし、要素オブジェクトもjQueryで取得しているなら、jQueryのeachメソッドで処理したほうがいいんです。で、処理には関数(function(){…});)を書けばいい。
EdgeとIEや、画像がキャッシュ対策
EdgeとIEや、画像がキャッシュされてる場合は、そもそも、このloadを捉えられないときがあります。
複数回ページの更新を行おうとした場合に、Internet Explorer (IE) では最新の情報を表示できないのです。これは、IE が同一の要求をキャッシュで処理しようとするためです。
参考:Internet Explorer によるコンテンツ キャッシュを無効にする
https://code.msdn.microsoft.com/jQuery-howto-10502352
この問題を解決するには、下記のように書くといいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$(function(){ var loadCount = 0 //loading状況の初期化 $("img").each(function(){ var img = new Image(); img.src = $(this).attr("src"); var cnt = 0; var timer = setInterval(function(){ cnt++; if(img.width > 0 || cnt > 10){ if(cnt > 10){ cnt = 0; } clearInterval(timer); imgLength = $("img").length; //読み込む画像の数を取得 loadCount++; // ここでバーを伸ばしていく // ロゴを出す。フェードイン if(loadCount == imgLength){ // バーをfadeOutする。 // $("バーのjQueryオブジェクト").fadeOut("fast"); // ロゴをフェードアウトする。 // ここは、Animate.cssのzoomOutを使いたい // $("バーのjQueryオブジェクト").fadeOut("fast"); $(".st-header_Wrapper").delay(1200).slideDown("slow"); $(".js-Slide_Contents").delay(600).fadeIn("normal"); } } }, 50); }); }); |
loadCount
:読み込まれていく画像の数をカウントするための変数
$("img").each(function()
:imgタグそれぞれに対して、functionの中を実行しなさいという命令
1 2 |
var img = new Image(); img.src = $(this).attr("src"); |
new Image()での、画像読み込み表示
jQueryで、画像読み込み表示には色々な方法はありますが、javascriptの new Image()を使用して表示させる例が多いみたいです。
参考 http://pops-web.com/main/pops/archives/189
1 |
img.src = $(this).attr("src"); |
$(this)は、ある1つのimgタグを指しています。で、これは、イメージオブジェクトを作成して、そのimgタグのsrcを、イメージオブジェクトのsrcに設定します。これにより、イメージオブジェクトのwidthで、画像が読み込まれたときに、imgタグのwidthと連動することになります。
なぜ、widthを見るかと言うと、imgタグには、widthが指定されているので、widthは、変わらないのですが、イメージオブジェクトは、widthを指定しなければ、画像が読み込まれた時に、widthが0以上の値を持つようになるからです。
1 2 |
var cnt = 0; var timer = setInterval(function(){ |
これは、イメージオブジェクトがwidthを持つまで、widthが0以上かどうかを調べるためのタイマーです。
cntは、タイマーの繰り返し回数を数えていて、10回を超えたら画像が読み込まれたものとします。画像が読み込まれるのが失敗することもあるかもしれないので、そのときには、バーを伸ばしてスルーします。
1 2 3 4 |
if(img.width > 0 || cnt > 10) { cnt = 0; } clearInterval(timer); |
これは、画像が読み込まれたら、つまり、widthが0以上の値をとった時の処理をわけています。画像が読み込まれたら、タイマーを止めています。
1 2 3 4 |
imgLength = $("img").length; //読み込む画像の数を取得 loadCount++; if(loadCount == imgLength){ //ここに全画像が読み込まれたときの処理を書く。 |
imgLength
でイメージが読み込まれているので、1つイメージが読み込まれましたよとloadCount++;
これで、1つカウントしてます。
そして、loadCount == imgLength
これで、読み込まれた画像の数と、ページの全体の数が同じなら、全部画像が読み込まれているのがわかるので、コンテンツをフェードイン、メニューをスライドダウンなどの処理を書きます。
},50);
タイマーは、50msで動いているので、もし仮に画像がキャッシュにあってすでに読み込まれていたとしても50ms間隔で、最低、画像の数、線は、段階的に100%まで伸びて行きます。
ここまでを整理するとこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$(function(){ var loadCount = 0 //loading状況の初期化 $("img").each(function(){ var img = new Image(); img.src = $(this).attr("src"); var cnt = 0; var timer = setInterval(function(){ cnt++; if(img.width > 0 || cnt > 10){ if(cnt > 10){ cnt = 0; } clearInterval(timer); imgLength = $("img").length; //読み込む画像の数を取得 loadCount++; // ここでバーを伸ばしていく // ロゴを出す。フェードイン if(loadCount == imgLength){ // バーをfadeOutする。 // $("バーのjQueryオブジェクト").fadeOut("fast"); // ロゴをフェードアウトする。 // ここは、Animate.cssのzoomOutを使いたい // $("バーのjQueryオブジェクト").fadeOut("fast"); $(".st-header_Wrapper").delay(1200).slideDown("slow"); $(".js-Slide_Contents").delay(600).fadeIn("normal"); } } }, 50); }); }); |
4.「1~3」を組み合わせる。
最終的に完成したJSが、以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
$(function(){ $(".header").css("display","none"); $(".contents").css("display","none"); }); $(function(){ // loading状況の初期化 // loadCount:読み込まれていく画像の数をカウントする変数 var loadCount = 0; // なんかの初期化 var aCnt = 0; // ローディング中、中央に現れる画像 $(".loading-Img").delay(1000).fadeIn("slow"); // imgが読み込まれるタイミングを監視 // Edgeと、IEキャッシュ問題の解消 // imgタグそれぞれに対して、functionの中を実行しなさいという命令 $("img").each(function(){ // イメージオブジェクトを作成している // $(this)は、ある1つのimgタグを指しているので、そのimgタグのsrcをイメージオブジェクトのsrcに設定 // これにより、イメージオブジェクトのwidthで、画像が読み込まれたときに、imgタグのwidthと連動することになる // なぜ、widthを見るか、読み込まれの指標としたいから var img = new Image(); img.src = $(this).attr("src"); // イメージオブジェクトがwidthを持つまで、widthが 0以上かどうかを調べるためのタイマー // cntは、タイマーの繰り返し回数を数えていて、10回を超えたら画像が全て読み込まれたものとする // 画像が読み込まれるのが失敗することもあるかもしれないので、そのときには、バーを伸ばしてスルーする var cnt = 0; var timer = setInterval(function(){ cnt++; // || は or // これは、画像が読み込まれたら、つまり、widthが0以上の値をとった時の処理をわけている // 画像が読み込まれたら、タイマーを止めています。 if(img.width > 0 || cnt > 10){ cnt = 0; // setInterval内の関数を止める処理 clearInterval clearInterval(timer); // 読み込む画像の数を取得 // これで、読み込まれた画像の数と、ページの全体の数が同じなら、全部画像が読み込まれているのがわかるので // コンテンツをフェードイン、メニューをスライドダウンなどの処理のタイミングの指標になる imgLength = $("img").length * 10; //imgLengthは170読み込まれることになる loadCount++;//1~17枚まで増える $(".loading-Bar").css({ //読み込まれた画像の数を画像全体で割り、%としてローディングバーのwidthに設定 "width": (loadCount / imgLength) * 100 + "%" }); } }, 50); // タイマーは50msで動いている。画像がキャッシュにあっても50ms間隔で画像の数、線は段階的に100%まで伸びて行く var tndST = setInterval(function(){ loadCount++; aCnt++; if(aCnt > 9){ clearInterval(tndST); aCnt = 0; } else { $(".loading-Bar").css({ //読み込まれた画像の数を画像全体で割り、%としてローディングバーのwidthに設定 "width": (loadCount / imgLength) * 100 + "%" }); } if(loadCount > imgLength){ //100%読み込まれたらローディングバーを隠す // バーをfadeOutする。 $(".loading-Bar").delay(1800).fadeOut("normal"); // ロゴをフェードアウトする。 $(".loading-Img").delay(2000).fadeOut("normal"); $(".contents").fadeIn("slow"); $(".header").delay(2200).slideDown("slow"); } },320); // タイマーは320msで動いている。画像がキャッシュにあっても320ms間隔で画像の数、線は段階的に100%まで伸びて行く }); }); |
追加したCSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
img.loading-Img { width: 30%; height: 20%; } .loading-Bar { position: fixed; top: 0; left: 0; background: #111111; height: 4px; -webkit-transition: all 0.4s linear 0s; transition: all 0.4s linear 0s; width: 0; } .loading-Img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; } |
追加したHTML
1 2 |
<div class="loading-Bar"></div> <img class="loading-Img" src="./img/dog_logo.png" alt="ロゴ" style="display:none;"> |
ここまでのコミット
追加したJSのこれや
1 |
imgLength = $("img").length * 10; |
1 2 3 4 5 6 7 8 |
var tndST = setInterval(function(){ loadCount++; aCnt++; if(aCnt > 9){ clearInterval(tndST); aCnt = 0; } |
これは、バーを滑らかに動かす(伸ばす)ためのコードです。
1 |
imgLength = $("img").length * 10; |
とすることにより、たとえば、17枚の画像(コンテンツ内の16枚の画像 + ロゴの画像)の10倍の170分割でスライダーを動かすことにしています。
するとスライダーが滑らかになります。しかし、1枚をあたかも10枚画像があるように処理しているため、1枚目の処理はもともとありましたが、残りの9枚の処理がありません。
そこで、これの登場です。
1 2 3 4 5 6 7 8 |
var tndST = setInterval(function(){ loadCount++; aCnt++; if(aCnt > 9){ clearInterval(tndST); aCnt = 0; } |
aCntは、0から8まで動きます。つまり残りの9枚を処理しています。9枚の処理が終わったら、つまりif(aCnt > 9){
なら、タイマー処理を終了して、次の画像へ処理がうつります。
今回、画像が、ほんとうに読み込まれているかどうか調べていますが(実際に画像をサーバーからいくつか削除して動かしてみると(キャッシュに入っていない限り)かくかくとバーが動くはず)、ネットワークが遅くなるなどして(めったにありませんが)上記処理のおかげで、画像がばらばらと表示されていくのは防げます。delayを大きくすればそれでも防げますが、それだとほんとうにホームページが表示されるのが遅くなります。
疑問「今回イメージオブジェクトを作成しましたが、なぜ作成するのか?」
それを作成しないと(イメージオブジェクトのwidthが見れないと)画像が読み込まれたかどうかの判定ができないからなのか。
あと、$(this)は、ある1つのimgタグを指しているので、そのimgタグのsrcをイメージオブジェクトのsrcに設定するが、これも、なぜそうするのか?
これですが、内部処理を見ているわけではないので、実は確実なことは、わかりません。しかし、こうやると、うまくからこうしてるって感じです。
たとえば、imgタグのwidthを見て入れば良いのでは?と思うかもしれませんが、imgタグにwidthがCSSで設定されていたらwidthは、初めから0以上の値を持ちます。しかし、イメージオブジェクトの方は、srcを設定しているだけなので、キャッシュも含めて、画像が読み込まれた場合のみ、widthが設定されるようです。
イメージオブジェクトにwidthの情報が入る時は、全ての画像が読み込まれたタイミングだと思ってまちがいない??
全ての画像ではなく、1枚の画像です。
1 |
$("img").each(function() |
すべての画像は、上記で、動かしている。
1 2 |
if(img.width > 0 || cnt > 10){ cnt = 0; |
なので、17枚画像があれば、cnt=0は、17回実行されます。すなわち、eachとは、imgタグすべてに対して、function()をそれぞれ実行しなさいよというメソッドです
最終的に出来上がったページ
https://ryosuketter.github.io/page_loading_effects/