とあるサイトを高速化したいという依頼を受けた。
構造
そのサイトは静的ページなんだけど、記事本文部分が以下の様なHTML構造になっていて、
<div>冒頭ぶった切り</div>
<div class="MoreBtn hide">続きを読むボタン</div>
<div class="MoreBody hide">記事の後半全文</div>
Javascript で DOMContentLoaded
のタイミングでクエリをチェックして mode=all
が付いてるかどうかを見て hide
クラスの除去を行っていた。
readmore.js(明用簡略ソースです)
$(function(){
if(/mode=all/.test(location.search)) {
$(".MoreBtn").removeClass("hide");
} else {
$(".MoreBody").removeClass("hide");
}
});
一応これで表示非表示の制御自体は期待通りの動作は実現できていました。
問題点
- このページにはサイドバーやサードパーティ製の広告タグがわんさか入っていました。
- そのため
DOMContentLoaded
が発火するのが非常に遅い 時がありました。 - するとヘタしたら続きを読むボタンや本文表示自体が冒頭文章が表示された後1分ぐらいしてからボタンや本文が唐突に現れるといった具合です。
-
広告タグや
readmore.js
を埋める場所を動かすのも難しい事情があるようです。 - 色々突っ込みどころはありますが現状は大きく変えずに小手先の修正で改善したいというのが要件です。
解決策
実はDOM内に記事部分が出現するタイミング自体は結構早いです。
問題の元凶は DOMContentLoaded
が発火するのが非常に遅い事で、またそれを早くするのも難しそうというという事です。
なので DOMConentLoaded
を待たずに .MoreBtn
や .MoreBody
の要素が出現したタイミングで JS 処理を実行すればいいんじゃね?
ってことでそれを発火するタイミングを早くする為に readmore.js を以下のような書き換えてみました。
!function(){
// selectors で指定された要素の出現、またはDOMContentLoadedの早い方のタイミングで handler を実行する、
function waitElementOrReady(selectors, handler) {
var loop = setInterval(function(){
if(selectors.every(function(v){return document.querySelector(v)})) {
onReady();
}
}, 500);
document.addEventListener('DOMContentLoaded', onReady);
function onReady() {
clearInterval(loop);
handler();
}
}
// $(...) の代わりに waitElementOrReady 関数経由で発火タイミングを指定する。
waitElementOrReady([".MoreBtn", ".MoreBody"], function(){
if(/mode=all/.test(location.search)) {
$(".MoreBtn").removeClass("hide");
} else {
$(".MoreBody").removeClass("hide");
}
});
}();
こんな感じの改造で、コンテンツ全体の読み込みが終わらなくても続きボタンや続きの本文が即座に表示できるようになりました。
案外簡単に出来てよかった。めでたしめでたし。