#前提
- gulpにてscssとejsを使用
- scssは各ページのファイルなどを全て一つのファイルstyle.scssに読み込んでおり、それをstyle.cssへとコンパイルさせて一つのcssファイルを全ページに読み込ませている。
- クラス名の被りが無いように、各ページのコンテンツ部分をdivでラップし、そのディレクトリ名をidとして付与することで個別化を図っている。
- つまり各ページのスタイルの記述はscssにてディレクトリ名のIDで囲われている。トップページなら#top、会社概要なら#companyと言った形。
- IDの付与はjsで実装しており、共通のjsファイル(common.jsのような形)で全ページに読み込ませている。
テキストではわかりにくいのでソースを詳しく確認したい場合は再調査へ
#発端
トップページのみローディング画面を実装したが、読み込み時に一瞬だけスタイルが適用されていない画面が写り、その後ローディング画面が表示される。色々調べてみたらスタイル未適応の画面が一瞬ちらつく現象はFOUC(参照)というらしい。
FOUCは主にcssをbody内のそれも後ろの方で読み込んでしまった時に起きる(DOM構築後にCSSが呼び出される為)が、今回は問題なくhead内にlinkタグを設置している。
まるで理解ができなかった。
#調査
ローディング画面を実装していることもあって、jsの発火タイミングに謝りがあるのかと思い改めてソースを確認した。
<!--省略-->
<link rel="stylesheet" href="/css/style.css"><!--各ページのスタイルを一つにまとめたcss-->
<script src="/js/common.js"></script><!--共通部分のjs-->
<script src="/js/top.js"></script><!--トップページのみ読み込ませているjs-->
</head>
<body id="top"><!--jsによって付与-->
<div id="loader"></div>
<div id="main_contents">
<header>
</header>
<div class="content">
<!--コンテンツ部分-->
</div>
<footer>
</footer>
</div>
</body>
$(function() {
var h = $(window).height();
$('#loader').height(h).css('display', 'block');
});
$(window).load(function() {
$('#loader').delay().fadeOut(600);
$('#main_contents').css('display', 'block');
});
あらかた上記のような感じ。
予め共通部分を含め丸っとラップしたコンテンツ部分(#main_contents)をdisplay:noneにしておき、DOM構築直後にローダーを表示。
画像等全ての要素が読み込めたらローダーをフェードアウトするといった仕組み。
この順序だと
- DOM構築前にcssが読み込まれる。
- ローディングのスクリプトは$(function(){});で囲ってあるのでまだ発火しない。
- body部分が読み込まれDOMが構築される(スタイルも適用されている)。
- DOMが構築されたので$(function(){});部分が発火。画面いっぱいにローダーが表示される。
- 画像等全て読み込まれたら$(window).load(function(){});部分で囲われた部分が発火。ローダーはフェードアウトしていく。
となるはず。
ちなみに、$(function(){});と$(window).load(function(){});の違いはこちらを参照。
何度かcssやjsの読み込み順を並べ替えたりしたものの、何も変わらない。なぜ…?
#発見
ぼーっとしながら何度かリロードしていると、ある一つの事実に気が付いた。
確かに読み込み時一瞬だけスタイルが適用されていない画面がちらつくが、
ヘッダーはスタイルが適用されている…
何故か共通部分はスタイルが効いた。しかしトップページの中身だけスタイルが聞いていない。
………………………………なぜ???
#再調査
改めてソースを見直してみた。前提の項目でも記載したが、そもそも各ページに各ディレクトリ名のIDをbodyに付与している。そしてscssにてそのID名からスタイルを記述しているので、その仕様が読み込み順に何か関係しているのかもしれない。
@charset 'UTF-8';
@import '_reset.scss';
@import 'objects/**/*.scss';
@import 'pages/**/*.scss';
上記objectsやpages内に共通部分である_header.scssや_footer.scss、トップページを含めた各ページのscssファイルを配置し、style.scssにまとめて読み込んでstyle.cssへとコンパイルしている。
#top {
p {
span {
}
}
}
#about {
p {
span {
}
}
}
簡単に説明するとscssの構造は上記のような感じ。bodyにそのディレクトリ名を付与し、scssでの記述にて一番の親セレクタをそのIDに指定してスタイルを記述することによって、style.cssに一つにまとめてもスタイルの被りを防いでいる。
そのIDをどうやって付与しているかというと、
$(function() {
var loc = location.pathname;
if (loc == "/") {
$('body').attr('id', 'top');
} else {
var $dir = location.href.split("/");
var $dir2 = $dir[$dir.length -2];
$('body').attr('id', '$dir2');
}
});
ドキュメントのパスを取得し、それがルートならボディにトップのIDを付与。
それ以外(各配下ページ)なら各ディレクトリ名を付与。
これでクラス名のダブりなど気にせずcssの記述が出来る。
…筈だった
#解明
上記の何がダメだったのか、それはIDの付与をDOM構築後に行ってしまっているという事。
いや、そもそもそうしないと付与は出来ないんだが。
しかし、cssはDOM構築前に読み込まれている。
これが何を意味するかというと、#topというIDが無い時点でCSSを読み込んでも、まだそのIDが存在していない。
つまり時系列的には
- DOM構築前にcssが読み込まれる。
- ID付与のスクリプト(common.js)は$(function(){});で囲ってあるのでまだ発火しない。
- ローディングのスクリプト(top.js)は$(function(){});で囲ってあるのでまだ発火しない。
- body部分が読み込まれDOMが構築される(#topはbodyに付与されていない)。
- #topが存在しないのでスタイルが適用されず、htmlがそのままレンダリングされた画面が表示される
- DOMが構築されたので$(function(){});部分が発火。ここで初めて#topがbodyに付与される。
- ローディング画面も表示。
- 画像等全て読み込まれたら$(window).load(function(){});部分で囲われた部分が発火。ローダーはフェードアウトしていく。
- ローディングで見えないが、#topは既にbodyに付与されているので、ローダー非表示後は問題なくcssが適用された画面が表示されている。
以上が今回発生した事象の原因。
#対策
scssを一つにまとめID別に適用したうえで、DOM構築後にbodyにIDを付与しない。これに尽きる。
どうしてもこの仕様で行いたいならcssとjsの読み込み順を逆にするといったところでしょうか。
あれ、でもこの場合$(function(){});で囲われた部分とcssはどっちが優先されるんだ?
DOM構築後に発火して次にcssが読み込まれるのか、結局読み込み順変えても変わらないのか。試してないからわからん。
共通部分(ヘッダー等)はまた別途に_header.scssなどでスタイルを記述している(#topに含まれていない)ので最初からスタイルが適用されていた模様。
どの道ID付与はDOMが構築されないと行われず、scssがIDで囲われた部分にスタイルを当てているならこの方法はだいぶ相性が悪いです。
なので今回は#main_contentsに直接スタイルを当てました。一瞬で治った。
こんなん分かるかよ