JSでアコーディオンを作成するときに、対象の要素に対して.on
クラスをつけて状態を判別したりしていますが、なんだかなーと思ったので変更してみました。
WAI-ARIAを使用すれば要素の状態を明示できるので、余計なクラスがつかずにいい感じかなーと。
WAI-ARIAについて
@h_plum さんの記事がとても参考になるので、こちらでご確認いただくと良いと思います。
アクセシビリティを意識したWAI-ARIA実装について
デモ
まずは出来上がったものをどうぞ。
See the Pen wai-aria accordion by かぶきち (@cubkich) on CodePen.
ではそれぞれ解説していきます、
HTML
<div class="wrapper" role="tablist">
<h3 id="acdTab01"
data-acd="tab"
role="tab"
aria-expanded="true"
aria-controls="acdPnl01"
aria-selected="true">大見出し<span class="icon"></span>
</h3>
<div id="acdPnl01"
data-acd="tabpanel"
role="tabpanel"
aria-labelledby="acdTab01"
aria-hidden="false">
<dl>
<dt>中見出し</dt>
<dd>テキストテキストテキスト</dd>
</dl>
</div><!-- /#acdPnl01 -->
</div><!-- /.wrapper -->
カスタムデータ属性
data-acd="tab", data-acd="tabpanel"
特に意味はないのでここは単純にclassでも良いかもです。
role属性
role="tab", role="tabpanel"
アコーディオンのタブ部分であること、パネル部分であることを明記しています。
hタグにrole属性つけるのってどうなんでしたっけ。div
につけるのは開発者、クローラーの両方にとって良いことだと思います。
WAI-ARIA属性
aria-controls
aria-controls="acdPnl01"
アコーディオンのタブが、どのパネルに紐付いたものかを明示しています。
今回は#acdPnl01
に対するタブですよーってことです。アコーディオンに限らず、タブアクションで使えそう。
aria-selected
aria-selected="true"
フォーカスされているかの状態ですが、アコーディオンでいうと開いているかどうか、みたいなところです。
aria-labelledby
aria-labelledby="acdTab01"
ラベルの明示です。今回は大見出しがラベルにあたるので、#acdTab01
の要素に向けられています。
aria-expanded
aria-expanded="true"
対象の要素が展開されているかどうかの状態を表します。
今回は読み込み時点で展開されているのでtrue
となっています。
開閉するたびに切り替わります。
aria-hidden
aria-hidden="false"
こちらは要素が隠れているかどうかの状態です。
aria-expanded
と反対の値が入ります。
感想
これだけ指定するものが多いのであれば、従来どおりにクラス振ったほう楽じゃね?って思います。
が、WAI-ARIA属性が振られていることでレンダリングされていない状態のHTMLを見ただけでもどういう状態なのかがわかるので、HTMLだけ読むひととか、クローラーに対しては優しいのかなーと。
SCSS
そんなに特殊なことはしていなかったの割愛します。
今回[data-acd="tab"]
とか[data-acd="tabpanel"]
に直接スタイル当てちゃったけど、他の見た目のアコーディオン作るときに困りそうだからやっぱりクラス振ったほうが良いな、と思いました。
jQuery
まずはアコーディオンに使用する要素を変数に入れます。タブを$_trg
、パネルを$_pnl
とします。
HTMLのときと同様ですが、ここでdata属性を使用してしまうと他の動きするアコーディオン作るとき大変かもです。
その他に、アニメーションの制御に使用するフラグも用意します。
anm_flg
はアニメーションが連続で実行されないように、アニメーション中はtrue、それ以外はfalseを返すように設定し、trueが返ってきたときはアニメーションを実行しません。(stop()
とか使っても良いかもです。)
const $_trg = $("[data-acd=tab]"),
$_pnl = $("[data-acd=tabpanel]");
let anm_flg;
初期設定
ページを読み込んだタイミングでアコーディオンが開いた状態にします。
デモはPC表示だけですが、実際に作ったものはSPのとき閉じているという仕様だったので、その名残です。
JS使わないでも大丈夫な部分ですね。
const acd_init = () => {
anm_flg = false;
$_pnl.show();
add_pnl_attr($_pnl);
$_trg.attr("aria-selected", "true");
};
パネルの開閉に合わせて属性の変更
アコーディオンを開いたときのWAI-ARIAの属性を変更します。
動きには直接関係ないですが、開いてますよー、閉じてますよーっていう情報を変更しています。
// パネルを開いたときの属性に変更
const add_pnl_attr = $_elem => {
if (!$_elem) return;
$_elem.attr({
"aria-hidden": "false"
});
};
// パネルを閉じたときの属性に変更
const rm_pnl_attr = $_elem => {
if (!$_elem) return;
$_elem.attr({
"aria-hidden": "true"
});
};
パネルの動き
タブ要素のaria-controls
でパネルの要素が指定されているので、紐付いているパネルをslideDown()
させると同時に、タブ要素のaria-selected
属性の状態を変更します。
開く/閉じるでそれぞれ逆の動きをしているので、開く/閉じるを引数のtrue/falseとかにしても良かったかもですね。
// パネルを開くときの動き
const open_action = $_tab_elem => {
if (anm_flg) return;
anm_flg = true;
const $_pnl_elem = $("#" + $_tab_elem.attr("aria-controls"));
$_tab_elem.attr({
"aria-expanded": "false",
"aria-selected": "true"
});
$_pnl_elem.slideDown(function() {
anm_flg = false;
});
rm_pnl_attr($_pnl_elem);
};
// パネルを閉じるときの動き
const close_action = $_tab_elem => {
if (anm_flg) return;
anm_flg = true;
const $_pnl_elem = $("#" + $_tab_elem.attr("aria-controls"));
$_tab_elem.attr({
"aria-expanded": "false",
"aria-selected": "false"
});
$_pnl_elem.slideUp(function() {
anm_flg = false;
});
rm_pnl_attr($_pnl_elem);
};
クリックしたときの指示出し
タブ要素をクリックしたとき、aria-selected
属性の状態をもとに開く/閉じるどちらの関数を実行するか判別しています。
関数を一つにしてtrue/falseにするときは、何かしら変数とかに代入して関数を実行するみたいになりそうですね。
// トリガーをクリックしたとき
$_trg.on("click", function() {
const _self = $(this);
_self.attr("aria-selected") === "true" ? close_action(_self) : open_action(_self);
});
実行
あとはページを読み込んだタイミングとかで実行すれば、クリックしたタイミングで動いてくれるはずです。
acd_action();
まとめ
WAI-ARIAなんてものがあるんだなーと試しに使ってみましたが、意味を覚えて使わないと普通に作るより時間かかるし、違う意味で捉えられる可能性もあるので、ちゃんと調べて覚えてからじゃないと使えこなせそうだなーといった印象です。
使ったほうがセマンティック的に良いかなーだけど、そんなに意識しないサイトだったらただのコストなので使う必要ないか、というところでしょうか。
あ・・間違いなどあったら指摘していただけるとありがたいです(´・ω・`)