:focus-within 疑似クラスを IE11 でも使用可能にする Polyfill 書いてみた

CSS の :focus-within 疑似クラスを使用した実装をするにあたり、IE11 向けに JavaScript で Polyfill を書くはめになったのですが、折角なのでシェアしようと思います。

以前、:focus-within 疑似クラスが便利ですよっていう記事を書いたんですけども、IE11 では動作しないため、お仕事などで IE11 が動作確認対象ブラウザに入っていたりするとちょっと面倒でした。

が、先日ちょっとわけがあって :focus-within 疑似クラスを使用した実装をするにあたり、IE11 向けに JavaScript で Polyfill を書くはめになったため、折角なのでということでシェアしようと思います。

ソースコードは下記に置いてあるので、そちらを見ていただければ終わる話なのですが......

今回は、IE11 だけを対象に、JavaScript ファイルを 1つ読み込めば終わる形にしました。「対象要素と附与する class 名の設定」 部分だけ変更して使ってもらう感じです。

'use strict'
    ; (function () {
        //IE11だけ動作させる
        if (document.uniqueID && document.documentMode == 11) {

            // 対象要素と附与する class 名の設定
            const focusItemClass = '.is-dropdown';
            const focusActiveClass = 'focus-is-active';

            // 対象要素を取得
            const focusItems = document.querySelectorAll(focusItemClass);

            // 取得した対象要素にイベントリスナーを登録
            Array.prototype.forEach.call(focusItems, function (e) {
                e.addEventListener('focusin', focusinListener);
                e.addEventListener('focusout', focusoutListener);
            });

            // 対象要素内にフォーカスがある場合に focusActiveClass を附与
            function focusinListener(event) {
                event.target.closest(focusItemClass).classList.add(focusActiveClass);
            };

            // フォーカス中の要素に focusItemClass が含まれていない場合、すべての対象要素から focusActiveClass を削除
            function focusoutListener(event) {
                if (!document.activeElement.classList.contains(focusItemClass.slice(1))) {
                    Array.prototype.forEach.call(focusItems, function (e) {
                        e.classList.remove(focusActiveClass);
                    });
                }
            };

            // IE11 で closest が動作しないので Polyfill
            if (!Element.prototype.matches) {
                Element.prototype.matches = Element.prototype.msMatchesSelector ||
                    Element.prototype.webkitMatchesSelector;
            }
            if (!Element.prototype.closest) {
                Element.prototype.closest = function (s) {
                    let el = this;
                    do {
                        if (el.matches(s)) return el;
                        el = el.parentElement || el.parentNode;
                    } while (el !== null && el.nodeType === 1);
                    return null;
                };
            }

        }
    }());

:focus-within 疑似クラスの Polyfill は他にもありますし、わざわざ再開発するようなものでもないのですが、対象要素を class 名で指定するなど、自前で使いやすい形にしたかったので書いた次第です。

あと、普段は ES6 で書いて Babel 通すっていう手順なんですけども、今回は IE11 しか対象にしていないので、最初から IE11 だけに向けて書くっていう面倒な事をしましたけど、あぁ closest() 動かないんだ...... とか focusItems.forEach(... とか書いてサポートしてねぇよって怒られたり辛かったです。

さて愚痴はいいとして使い方ですが、例えば、下記のようなリストがあって、これを :focus-within 疑似クラスを使用したプルダウンメニューにしたい場合、

<ul class="menu-list">
    <li class="is-dropdown">
        <a href="#">MenuItem01</a>
        <ul>
            <li><a href="#">SubMenuItem</a></li>
            ...略...
        </ul>
    </li>
    <li class="is-dropdown">
        <a href="#">MenuItem02</a>
        <ul>
            <li><a href="#">SubMenuItem</a></li>
            ...略...
        </ul>
    </li>
</ul>

対象にしたい要素に class="is-dropdown" (変更可) を付けて、あとは下記のように CSS を書いてあげればよいと。

/* IE11 以外のモダンブラウザには :focus-within 疑似クラス使えば OK */
@supports selector(li:focus-within) {
    .menu-list > li:focus-within > ul {
        ...略...
    }
}

/* IE11 は Polyfill で class が付くので、それに合わせたスタイルを書けばよい */
.focus-is-active > a + ul {
    ...略...
}

何かおかしいところがあれば指摘して頂けるとありがたいです。

関連エントリー

記事をここまで御覧頂きありがとうございます。
この記事が気に入ったらサポートしてみませんか?