使用ケース
執筆時点の Web 版の Youtube や Gmail は、左側にメニューがあります。このメニューは、ハンバーガーアイコンをクリックで縮小します。
このような「ユーザーのクリックで表示を変える」メニューで、「その変更された表示をページを移動してもページを再読み込みしても維持する」方法の紹介です。
classList.toggle
で切り替えた class を Web Storage API の sessionStorage
で維持します。この方法で実装すると、再度ハンバーガーアイコンをクリックするか、ブラウザを閉じない限り、ユーザーが選んだ状態を維持し続けます。
尚、ブラウザを閉じてもデータを維持する localStorage
でもやることは同じです。
JavaScript のコード
今回の方法では、このようなことを行います。
- ボタンのクリックで <body> タグの class に change を付ける。
- もう 1 度ボタンをクリックすれば、<body> タグの class から change を削除する。
- <body> タグの class に change がある場合に、
sessionStorage
にデータを保存する。 sessionStorage
にデータがあれば、ページを移動した時やブラウザを再読み込みした際に <body> タグの class に change を付ける。
実際のコードは、このようなものです。まず、HTML には、以下の id が付いているとします。
- <body> タグには
body
- ハンバーガーメニューなど class を変更するためのボタンには
button
そして、JavaScript です。
const target = document.getElementById('body');
const button = document.getElementById('button');
const key = 'class';
const value = 'change';
const data = sessionStorage.getItem(key);
if (target && button) {
if (data) {
target.classList.add(value);
}
button.addEventListener('click', () => {
target.classList.toggle(value);
if (target.classList.contains(value)) {
sessionStorage.setItem(key, value);
} else {
sessionStorage.removeItem(key);
}
});
}
8 行目から 10 行目は、ページを移動した時やブラウザを再読み込みした時に <body> タグに class を付ける処理です。
if (data) {
target.classList.add(value);
}
sessionStorage
に保存したデータがあれば class を付けます。データがなければ class は付けません。
それ以降は、ボタンをクリックした際の処理です。13 行目で、ボタンをクリックする度に <body> タグの class に change の追加と削除をしています。
target.classList.toggle(value);
15 行目で <body> タグの class に change があるかを判定し、sessionStorage
の処理を変えています。
if (target.classList.contains(value)) {
sessionStorage.setItem(key, value);
} else {
sessionStorage.removeItem(key);
}
<body> タグの class に change があれば、sessionStorage
にキーが class
、値が change
のデータを保存します。<body> タグの class に change がなければ、sessionStorage
からデータを削除します。
あとは class の change を使い、CSS を指定します。これでページを移動しても、再読み込みをしても、サイトの状態を維持できます。
ブラウザを閉じても状態を維持する場合は、3 ヶ所の sessionStorage
を localStorage
に変更します。
尚、sessionStorage
や localStorage
は、デベロッパーツールのアプリケーションで検証できます。
AMP でも可能
今回の方法は、AMP でも使えます。使用するコンポーネントは amp-script です。
AMP での JavaScript は、WorkerDOM の仕様に沿う必要があります。WorkerDOM で許可されている DOM API は、WorkerDOM compatibility で確認できます。
ただ、注意点が 3 つあります。
1 つ目の注意点が、<amp-script> タグで <body> タグは囲めません。そのため、<body> タグの class を付け替えるのではなく、<body> タグ内の要素の class を付け替える必要があります。
<!-- これはダメ -->
<amp-script src="JavaScript ファイルのパス" layout="container">
<body>
</body>
</amp-script>
2 つ目の注意点が、「Eleventy(11ty) + AMP プラグイン」と「WordPress + AMP プラグイン」で試した限り、sessionStorage.removeItem
の動作が通常と異なります。キーが残り、値が #document
になります。
そのため、この部分を…。
sessionStorage.removeItem(key);
私は、このように変更し空の値で更新しています。
sessionStorage.setItem(key, '');
3 つ目の注意点が、amp-script の layout 属性で container が使えないことです。もし使えば AMP エラーは出ませんが、デベロッパーツールのコンソールで以下のエラーが出ます。
[amp-script] Blocked 1 attempts to modify DOM element attributes or styles. For variable-sized <amp-script> containers, a user action has to happen first.
Google 翻訳:[amp-script]ブロックされた1は、DOM 要素の属性またはスタイルを変更しようとします。可変サイズの <amp-script> コンテナの場合、最初にユーザーアクションを実行する必要があります。
このエラーは、<amp-script> タグに layout="container"
を指定しつつ、ユーザーのクリック操作なしで属性やスタイルを変更する際に起こります。Cumulative Layout Shift(CLS)の発生を防ぐのが目的です。
今回のように sessionStorage
で class を追加する場合は、子要素によって高さが自動的に調整される amp-list などを <amp-script> タグで囲む際に工夫が必要です。