Gatsbyではイベントリスナー系は効かない
5分目次
Gatsby で DOM 操作したい
てかまぁクラスの付与がしたいだけだったんですけどね
そこで JS から付与したり取り除こうと思い、行動に。Gatsby では、そういった DOM 操作などの JS はgatsby-browser.js
から行うのですが、これがなかなかに曲者だったという記事になります。
即時関数はもちろん、onload でも EventListener 系でもダメ
ちなみにですが今回やりたいことは DOM 操作でしたが、即時関数はもちろん onload でも EventListener 系も全滅です。DOM 操作に関する下記のようなコードをgatsby-browser.js
に書いても全滅するということですね。
// #tagsにクラス付与する関数(なければ「ないよ!」と返される)
const addClass = () => {
const target = document.querySelector('#tags');
if (!target) {
console.log('ないよ!')
}else{
target.classList.add('added');
}
}
// 即実行
addClass();
// DOM完成後に実行
window.addEventListener('DOMContentLoaded', addClass());
// 最後に実行
window.onload = addClass()
// 上3つの結果
ないよ!
ないよ!
ないよ!
その前に復習
復習要らない人はこちらのリンクをクリックで結論までジャンプ!
上のコード例でも軽く示していますが……
js のページ読み込み時での実行タイミングは下記 3 つが挙げられます。
- 読み込んですぐに実行(即時関数)
- DOM ツリー完成後に実行(DOMContentLoaded)
- 画像の読み込み等が終わって、最後に実行(onload)
ただし、即時関数で操作をするときは注意が必要で DOM 操作したい対象が即時実行スクリプト以前に登場してる必要があります。下記のように操作したい対象より前にスクリプトを書いても実行されません。
なので、下図のようにスクリプトを対象より後ろで実行するか、実行タイミングをDOMContentLoaded
などに変更する必要があります。
DOMContentLoaded
やwindow.onload
はブラウザがソースコードを上から下へと処理し終わった後にスクリプトを実行するのでスクリプトがどこにあるかは関係ないということですね
という共通認識を得た上で。Gatsby ではDOMContentLoaded
でもwindow.onload
でも DOM 操作がなぜかできないの不思議だねっていう。
結論
下記 2 つの解消法があります。
setTimeout
を使う(非推奨)- Gatby の API を使う(推奨)
解決法 1: setTimeout
を使う(非推奨)
あまり良い解決方法ではありませんが、setTimeout
で 1000〜2000 ミリ秒後に実行するようにするときちんと DOM 操作できます。
setTimeout(function() {
// ここにコード
}, 1000);
記事冒頭の例を使うならこうですね。
setTimeout(function() {
const target = document.querySelector("#tags");
target.classList.add("added");
}, 1000);
ただし、これだとページボリュームによっては実行できないページも出てくるかもしれません。
例えばページ A では 1000 ミリ秒以内にtarget
がレンダリングされても、ページ B では 1100 ミリ秒かかり、うまく動作しないことが懸念されます。『じゃあ 1200ms 後に実行すればいいじゃん』とやっても 1300 ミリ秒かかるページ C が出てきたらといたちごっこになりかねないのです。
解決法 2: Gatsby の API を使う(推奨)
これがベストですね。gatsby のドキュメントにありました。英語が読める人ならリンク先にいくのが一番良いかと思います。実行したい内容に応じて使う API を変えましょう。ここではとりあえず汎用性が特に高いもののみをまとめてみます。
onInitialClientRender
サイト閲覧者の一番最初のレンダリング終了時に実行。
exports.onInitialClientRender = () => {
// ここにコード
};
ただし、こいつだとページ遷移された際に再度実行されたりはしないので注意が必要ですね
onRouteUpdate
サイト閲覧者のページロード時に実行し、ページ遷移が行われた際にも実行。
gatsby 版のwindow.onload
と考えても良いかも?
exports.onRouteUpdate = ({ location }) => {
// ここにコード
};
この引数({location}
)が結構大事だったりします。
最初の例を使うならこんな感じ。
exports.onRouteUpdate = ({ location }) => {
if (location.pathname == "/hoge") {
const target = document.querySelector("#tags");
target.classList.add("added");
}
};
location.pathname
としてあげるとhttps://31navi.com/hoge
の/hoge
が得られます。「トップページではこの処理をしてこのページではこの処理を……」なんて場面にも対応できるので便利ですね。
ただ一点だけ気をつけたいのがhttps://31navi.com/hoge/
と URL の最後にスラッシュをつけてアクセスされてしまった時を想定する必要があります。
exports.onRouteUpdate = ({ location }) => {
if (location.pathname == "/hoge" || location.pathname == "/hoge/") { const target = document.querySelector("#tags");
target.classList.add("added");
}
};
今回僕はこの API で無事に DOM 操作できるようになりました