unitone のブロックリンク(ボックス全体をリンクにするやつ)の実装について

unitone にはブロック全体にリンクを設定できる機能があります(unitone ではこれをブロックリンクと呼んでいます)。ただ、WordPress はブロックの中にはブロックを入れることができるし、当然テキストにはテキストリンクが設定できるので、いわゆる「リンクの中のリンクどうする問題」が発生します。

これまでブロックリンクの実装は CSS でおこなっていたのですが、先日の unitone のアップデートで JavaScript を使った実装に変更しました。今回はなぜそのような変更をしたのか、どういう変更をしたのかについて書きたいと思います。

a 要素でラップする実装(非推奨)

これは unitone ではやっていなかったのですが、一応こういう実装方法もあるよということで紹介します。

例えばこういうふうに a でラップすることで、全体にリンクを設定することができます。

<a href="#">
    <div>
        これはブロックです。
    </div>
</a>

ただ、a の中に a を入れることはできないので、テキストリンクを設定することができません。

<a href="#">
    <div>
        これはブロックです。<a href="#">テキストリンク</a>は不可…。
    </div>
</a>

CSS による実装

ということで、unitone では不可視の a をボックス全体に広げる形で実装していました。
※わかりやすくするために一部実際とは変えています。

<div data-unitone-layout="decorator">
    <div data-unitone-layout="decorator__inner">
        <div>
            <p>これはブロックです。<a href="#">テキストリンク</a>も動作します。</p>
        </div>

        <a data-unitone-layout="decorator__link" href="#">さらに詳しく</a>
    </div>
</div>
[data-unitone-layout~="decorator"] {
    position: relative;
}

[data-unitone-layout~="decorator__inner"] {
    position: static !important;
}

:where([data-unitone-layout~="decorator__inner"] > :not([data-unitone-layout~="decorator__link"]) a) {
    position: relative;
    z-index: 2;
}

[data-unitone-layout~="decorator__link"] {
    position: static !important;
    display: block !important;
    height: 0 !important;
    width: 0 !important;
    text-indent: -99999px !important;

    &::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 1;
    }
}

まぁおおよそこれでも良いんですけど、positionz-index をごにょごにょしないといけないので、中に入れるブロックによっては干渉したりコントロールが難しかったりして、微妙だなーと感じていました。

JavaScript による実装

ということで、JavaScript を使って、ブロックをクリックしたらその中の不可視の a が自動的にクリックされるようにしたら良いんじゃないの?とひらめきました。

ただ、何も考えずに普通にやっちゃうとブロックに触れた段階でリンクしちゃうからテキストのコピーができないとか、キーボードユーザー・音声ブラウザユーザーが使いにくいとかがあると思うので、どういう実装が良いのかなと調べていたところ、下記の記事がとても参考になりました。

そして、なんかデザインに既視感があるなーと思っていたら、Every Layout の Heydon Pickering さんのサイトでした。すごい!

ということで、アップデートしてこういう実装に変更しました。

// JavaScript は苦手なのでもっとスマートな実装がありそう。
// もっと良い書き方があれば教えてください!

document.addEventListener( 'DOMContentLoaded', () => {
    const decorators = document.querySelectorAll( '[data-unitone-layout~="decorator"][data-unitone-layout~="-has-link"]' );

    [].slice.call( decorators ).forEach( ( decorator ) => {
        let down, up;
        const link = decorator.querySelector(
            ':scope > [data-unitone-layout~="decorator__inner"] > [data-unitone-layout~="decorator__link"]'
        );

        if ( !! link ) {
            decorator.addEventListener( 'mousedown', ( event ) => {
                event.stopPropagation();
                down = +new Date();
            } );

            decorator.addEventListener( 'mouseup', ( event ) => {
                event.stopPropagation();

                if (
                    [ 'A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA' ].includes(
                        event.target?.tagName
                    )
                ) {
                    return false;
                }

                up = +new Date();
                if ( up - down < 200 ) {
                    link.click();
                }
            } );
        }
    } );
} );
[data-unitone-layout~="decorator"] {
    position: relative;
}

[data-unitone-layout~="decorator"][data-unitone-layout~="-has-link"] {
    cursor: pointer;

    &:has(:focus-visible) {
        outline: auto;
        outline: auto -webkit-focus-ring-color; /* for Chrome */
    }
}

[data-unitone-layout~="decorator__link"] {
    position: static !important;
    display: block !important;
    height: 0 !important;
    width: 0 !important;
    text-indent: -99999px !important;
}

結構使い勝手が良かったので、Snow Monkey Blocks のボックスブロックにもブロックリンクの機能を追加しました。

inc2734のアバター

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です