こちらの記事に記載したコードについての解説です。 TwitterのLazyloadが必要な方はリンク先を参照してください。
functions.phpについて
Twitter Lazyloadに使用しているPHPのコードがこちら
add_filter('the_content', function($content) {
$content = str_replace('class="twitter-tweet"', 'class="lazy-tweet"', $content);
$content = str_replace('<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>', '', $content);
return $content;
});
これらのコードは単なる置換です。Twitterウィジェットは特定の条件に合致するHTML要素を持つと反応して読み込むので、まずは反応しないようにクラスを「twitter-tweet」から「lazy-tweet」に置き換えています。
合致条件に関してですが、クラス名とpタグ、aタグ、あとツイートIDが分かれば反応するようです。最低限必要な形としてはこんな感じでしょうか。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/user/status/xxxxxxxxxxxxxxxxxxx></a></blockquote>
また各ツイートの埋め込みタグについているScriptタグですが何度も読み込む必要はなく、また今回のコードでは別で読み込んでいますので不要です。
javascript.jsについて
Twitter Lazyloadに使用したJavaScriptのコードがこちら
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
const loadTarget = document.getElementsByClassName("lazy-tweet");
const loadOption = {
root: null,
rootMargin: "100% 0px 100% 0px",
threshold: 0
};
const loadCallback = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("twitter-tweet");
}
});
};
const loadObserver = new IntersectionObserver(loadCallback, loadOption);
for (let i = 0; i < loadTarget.length; i++) {
loadObserver.observe(loadTarget[i]);
};
window.addEventListener('load', function(){
twttr.widgets.load(
document.getElementsByClassName("lazy-tweet")
);
});
window.addEventListener('scroll', function(){
twttr.widgets.load(
document.getElementsByClassName("lazy-tweet")
);
});
今回のコードではスクロールに合わせてページの内容を変える JavaScript の Intersection Observer を利用しています。
まずはこちらのコード。
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
これはTwitterがデベロッパープラットフォームで公開しているコードです。
最高のパフォーマンスと信頼性を得るには、テンプレートにwidgets.jsスクリプトを含めます。
Twitter Developer Platform
最適なウェブページパフォーマンスを実現し、TwitterウィジェットJavaScriptイベントのトラッキングを可能にするために、Twitter for Websites JavaScriptをページテンプレートに一度だけ含めます。
複数のウィジェットを使用しているサイトでは、ページにTwitterウィジェットを一度設定することで、サイトが高速化され、コンテンツ管理システムを使用している場合は、埋め込みツイートなどのウィジェットが作者にとってより信頼性の高いものになります。
WordPressを利用している方であれば、シェアボタンなどでTwitterウィジェットを読み込んでいる可能性が高いので本来は不要ですが、今回のコードではPHPで各ツイートのスクリプトタグを削除していますので、読み込む機会がない場合に備え記載しています。
そして実際にLazyloadを実現しているコードがこちらになります。
const loadTarget = document.getElementsByClassName("lazy-tweet");
const loadOption = {
root: null,
rootMargin: "100% 0px 100% 0px",
threshold: 0
};
const loadCallback = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("twitter-tweet");
}
});
};
const loadObserver = new IntersectionObserver(loadCallback, loadOption);
for (let i = 0; i < loadTarget.length; i++) {
loadObserver.observe(loadTarget[i]);
};
「loadTarget」では監視する要素を指定。今回はfunctions.phpによって書き換えられた「lazy-tweet」を対象にしています。
そして「loadCallback」では監視対象となっている要素がスクロールによって画面内に入ってきた際の動作をしています。今回は入ってきた各要素のクラスに「twitter-tweet」を追加するようにしました。
loadOptionについて
コード自体はloadOptionがなくとも動作しますが、Twitter Lazyloadを導入した場合にはツイートを表示する際に
- スクロール
- 要素の表示
- ウィジェットの読み込み
- ウィジェットの適用・表示
と、このように表示まで複数の段階を踏むことになり、ページを閲覧するにあたり表示までの待ち時間が発生します。
そこで、要素が表示される前にウィジェットの読み込みを開始させることで閲覧時のストレスを軽減させることができるのが loadOption です。
rootでは対象となるビューポートの指定、rootMarginではビューポートの大きさを変更、thresholdではターゲットがどれくらいの割合で見えていたら実行するかのタイミングを変更できます。
今回のコードでは rootMargin で上下に100%ずつ先読みをする設定としているので、高さ1080pxのディスプレイであれば、表示されている部分に加えてさらに上下に1080px分が表示されている(ウィジェットの読み込みタイミングとなっている)ように認識されるということですね。
範囲を広げれば広げるほど先読みされる範囲が広がるためストレスなく閲覧できるようになりますが、各ツイートの読み込みには時間がかかりますので適切な範囲で設定するのがよいかと思います。
今回の設定は高さ1920pxのディスプレイ設定で読込時と閲覧時にストレスない範囲がこのあたりでした。高さを1080pxまで絞ればもう少し範囲を広げてもよいかもしれません。
具体的な変更を加えたい場合はこちらのページを参考にすると望み通りの形にできるかと思います。
addEventListenerでウィジェットを読み込む
最後に解説するのがこちら
window.addEventListener('load', function(){
twttr.widgets.load(
document.getElementsByClassName("lazy-tweet")
);
});
window.addEventListener('scroll', function(){
twttr.widgets.load(
document.getElementsByClassName("lazy-tweet")
);
});
「Intersection Observerを使っているのになぜaddEventListener?」と思われる方もいるかと思います。
ですが、実装する際に問題となったのが閲覧時のストレスで、解決するためにはこの方法がベストでした。
Intersection Observer内に twttr.widgets.load() を設置した場合、ウィジェットが読み込まれるタイミングは「スクロールが止まったタイミング」になります。
スクロールが止まった時点では、既に各ツイートのクラスが変更され、ウィジェットが読み込める状況ができあがっています。ここまでは問題ありません。
しかし、Twitterウィジェットは上から下へと順番に適用するといった動作をしません。表示される際には下から上へとなりがちで、閲覧者はページを下から読むことになります。
また、画像や動画などメディアを含むツイートと、文字だけのツイートでは表示されるまでのタイミングに明確な差があります。
文字だけのツイートが先に完成すると閲覧者はそちらに目が行くわけですが、後にメディアを含むツイートが完成すると表示位置にズレが生じて、読んでいたツイートが視覚から消えます。
ページを下から上へ読むだけでなく、読んでいたものが消えるとなればコンテンツを楽しむどころではありません。
そこで twttr.widgets.load() を addEventListener で1スクロールごとに読み込むことで、閲覧時のスクロールに合わせてウィジェットを読み込み表示させることができるよう変更しています。
この方法であれば Intersection Observer に要素が認識されたタイミングで addEventListener のウィジェット適用もかかるので、スクロールした通り、上から下へと順番にツイートが読み込まれます。
閲覧時の負荷上昇が起こるのでは?
Intersection ObserverがaddEventListenerと比べて優れているのはその負荷の軽さです。
addEventListenerではマウスの1スクロールごとにイベントが発生するのに対して、Intersection Observerでは要素が表示されるまではイベントが実行されないためCPU負荷が軽くなります。
ですが、今回の方法は1スクロールごとにウィジェットを読み込んでいるわけではありません。
addEventListener で読み込んでいるのは「ウィジェットの初期化機能」です。
Twitterウィジェットはページ内で一度読み込まれるとそれ以上は読み込まれなくなります。多くのサイトで遅延読み込みを適用させられないのはこの仕様のためと思われます。
それを解決するのが twttr.widgets.load() です。
ページがロードされた後に埋め込みコンテンツを初期化する
Twitter Developer Platform
ほとんどのTwitter for Websitesの統合は、セットアップドキュメントにある推奨埋め込みコードでうまくいきますが、Twitter for Websites JavaScriptウィジェットがページのDOMをスキャンして、ボタンやウィジェットに拡張できる新しいHTML要素を検出する方法とタイミングを最適化したい場合があります。
コンテンツが動的にページに挿入される場合(レイジーローディングコンテンツや記事間を移動するためのpushStateテクニックの使用など)、twttr.widgets.load() 関数を使用して新しいボタンやウィジェットをパースする必要があります。
ウィジェットを初期化することで再度DOMスキャンを行い、合致するHTML要素を見つけ出すことで遅延読み込みを実現しています。
このスクロールで行われるのはウィジェットの読み込みではなく、単なる初期化とスキャンなので負荷上昇は大きくありません。
それよりも実際に読み込まれるウィジェットの方が何倍も重いので気にする必要はないと考えています。
不要になった「lazy-twitter」を残す理由は?
Intersection Observer では埋め込みツイートのクラスを追加しましたが「lazy-twitter」のクラスは残していました。その理由は、ウィジェット初期化時のスキャン条件を「lazy-tweet」に結びつけているからです。
引数なしで呼び出された場合、widgets-js は初期化されていないウィジェットのために document.body DOM ツリー全体を検索します。よりよいパフォーマンスのために、要素の子だけに検索を制限するためにHTMLElementオブジェクトを渡してください。
Twitter Developer Platform
twttr.widgets.load(
document.getElementById("container")
);
と、このようにtwttr.widgets.load()ではDOMスキャン時に検索範囲を制限することができます。
それを用いて、まだウィジェットを読み込んでいないツイートをスキャンさせたのがこちら。
window.addEventListener('scroll', function(){
twttr.widgets.load(
document.getElementsByClassName("lazy-tweet")
);
});
「lazy-twitter」を削除してしまうと twttr.widgets.load() に使える引数は「twitter-tweet」となります。しかし、その場合は既にウィジェットが読み込まれているものも検索に引っかかります。
そこで確実にまだ読み込まれていない「lazy-twitter」を引数に設定することで検索時の負荷低減し、表示速度を上げています。
また、副次的な効果として、たまに起こるウィジェットが適用されないミスについても、スクロールすることで再度適用することが可能となっています。
Twitterウィジェットが読み込まれると、クラス名は「class=”twitter-tweet twitter-tweet-rendered”」となり、自然と書き換えられますので、あえて置換・削除する必要もありません。
クラス名変遷
- twitter-tweet(初期)
- lazy-tweet(PHP適用後)
- lazy-tweet, twitter-tweet(JavaScript適用後)
- twitter-tweet, twitter-tweet-rendered(Twitterウィジェット適用後)
なぜload時にaddEventListenerを?
スクロールした位置にツイートがあれば問題ないのですが、ページを読み込んだ初期位置にツイートがある場合にはLazyloadが機能せず、ウィジェットが適用されないままとなります。
閲覧者にコンテンツが存在しないと勘違いされることを防ぐため、 addEventListener でload時に読み込んでいます。
また、本来であればスクロール後には動作するはずなのですが、ページ読み込み後の初期位置にあるツイートにはウィジェットが適用されないバグがあるようです。
スクロールで解決する場合もあれば、ページの再読込が必要な場合もあり不安定でしたが、addEventListenerの設置により改善したので記述しています。動作を安定させるための措置です。
その他、想定される問題については別記事に記載しています。こちらをご覧ください ↓