先日公開した記事「CSS コンテナクエリ考察」のなかで、コンテナクエリの課題のひとつとして検証方法が確立していない点を取り上げました。

この問題を緩和させるために、対象要素をリサイズ可能にしてコンテナクエリ(@container)のスタイルを検証できる Bookmarklet「cq-resizer」を作成しました。

今回作成した Bookmarklet は、以下のリンクをブラウザのブックマークバーにドラッグ & ドロップすることで登録できます。

cq-resizer.js

Bookmarklet のコードは、以下の GitHub Gist にもアップしています。

Bookmarklet とは

Bookmarklet(ブックマークレット)は、ウェブブラウザのブックマークから JS のコードを実行する仕組みのことをいい、javascript: スキームを即時関数で実行することによって実現します。

javascript:(() => {
  /* 実行するスクリプト */
})();

例えば、以下は閲覧している記事の titleprompt で表示する Bookmarklet です。

javascript:(() => {
  prompt('title', document.title);
})();

まず、以下の手順で Bookmarklet を登録します。

  1. 適当なウェブページをブックマーク(このページでも可)
  2. ブックマークを編集して、URL を JS のコードに差し替える
  3. ブックマーク名をわかりやすい名前に変更(任意)

試しに、先ほどの Bookmarklet を登録して実行すると、ダイアログが表示されテキストフィールドにページの <title> が表示されます。

Bookmarklet を実行してダイアログが表示されている状態のスクリーンショット
Bookmarklet を Google Chrome で実行した例

デベロッパーツールのコンソールに、JS コード全体をペーストしても実行可能です。

なお、Bookmarklet は JS なので、以下の制約が挙げられます。

  • Content Security Policy(CSP)の指定内容によっては、スクリプトの実行が拒否される
  • ブラウザの拡張機能や、ドキュメントで読み込んでいる JS や CSS と競合する場合がある
  • <iframe> や Shadow DOM の内側には、基本的にはアクセス不可
  • 異なるオリジンのリソースには、基本的にはアクセス不可

また、ブックマークとして登録するときに 1 行のコードになるため、JS を記述するときには以下の点に気をつける必要があります。

  • 行末のセミコロン(;)を省略しない
  • コメントは複数行コメント(/* */)を使用する

以下は、コンテナクエリを指定したデモです。登録したブックマークを選択して Bookmarklet を実行してみてください。

Live Demo

LOREM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

LOREM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

LOREM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

LOREM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Bookmarklet を実行すると、以下のように 5 つの基準コンテナ(container-type プロパティが指定されている要素)の右下にリサイズハンドルが追加され、リサイズが可能になります。加えて、画面の右上にはインフォパネルが追加されます。

Bookmarklet を実行した状態のスクリーンショット。画面の右上にインフォパネル、基準コンテナにリサイズハンドルが追加される
Bookmarklet の JS を実行した状態

インフォパネルは以下の要素で構成されています。

インフォパネルのスクリーンショット。タイトル、Close ボタン、基準コンテナ、No.、セレクタ名、横幅、チェックボックスの項目が示されている
項目内容
タイトルBookmarklet の名称
Close ボタンインフォパネルを閉じるボタン
No.通し番号
セレクタ名基準コンテナの class もしくはタグ名
横幅基準コンテナの横幅(px)
チェックボックスリサイズのオン・オフ

タイトルをドラッグすることで、インフォパネルを移動できます。Close ボタンでインフォパネルを閉じると移動した位置はリセットされます。

セレクタ名にはアンカーリンクが付与されており、選択すると該当の基準コンテナの垂直位置までスクロール移動します。なお、セレクタ名は class 属性の値が表示されますが、class の指定がない場合には HTML の要素名を表示します。

横幅のフィールド(<input type="number">)と基準コンテナの幅は連動しているので、フィールドの数値から基準コンテナの横幅を変更することもできます。

チェックボックスでは、基準コンテナのリサイズ指定のオン・オフを切り替えることができます。

現時点では以下の問題を認識しています。

リサイズハンドルの表示

見出し「リサイズハンドルの表示」

Safari において、基準コンテナの上に背景を持つ子孫要素が重なると、リサイズハンドルが隠れてしまうことがあるようです。

なお、Google Chrome や Firefox のデバイスモード下では、リサイズハンドルが小さく表示されますが、かろうじてリサイズすることはできるようです。

基準コンテナが flex アイテムとして他の要素と並んでいる状態だと、リサイズハンドルでの操作や、フィールドの数値からスムーズにサイズを変更できないことがあります。

基準コンテナに style 属性を追加するため、すでに style 属性が付与されている場合には、スタイルが競合して動作や表示に影響を及ぼす可能性があります。

<iframe> の内部(「CodePen」のコードなど)や、ShadowDOM の内部で使用されている基準コンテナは対象外です。

ここからは、Bookmarklet の実装内容の一部を紹介します。

基準コンテナへのスタイル追加

見出し「基準コンテナへのスタイル追加」

まず、ページ内のすべての HTML 要素のなかで、CSS の container-type プロパティが指定されている要素(基準コンテナ)を検索し、見つからない場合にはメッセージを表示します。

次に、基準コンテナのなかで、親子関係にある要素で、水平・垂直位置が同じ場合には子孫要素を除外しています。これは、リサイズハンドルが重なることで操作が複雑になるのを避けたいのと、親要素側でリサイズできれば十分だというのが理由です1

続いて、このフィルタリングされた基準コンテナにスタイルを追加します。指定されている container-type の値に応じて、以下のようにスタイルを振り分けています。

  • inline-size の場合は、resize: horizontaloverflow-x: auto を追加
  • size の場合は、resize: bothoverflow: auto を追加

これにより、基準コンテナの右下にリサイズハンドルが表示され、ドラッグしてサイズを変更することができるようになります。

さらに、インフォパネル内のセレクタ名に付与されているリンクの遷移先となるアンカー要素を生成し、不可視化したうえで、position: absolute で配置しています。

インフォパネルは Shadow DOM として実装しています。

2024/06/18 追記

Shadow host(:host)は、外側のスタイルの影響を受けるので、all: revert-layer!important フラグとともに指定しています。

また、Shadow tree の内側のスタイルは、外側のスタイルから直接的には影響は受けませんが継承はされるため、影響を受けそうなプロパティに初期値を設定しています。

CSS の抜粋
:host {
  all: revert-layer !important;
}

@layer reset {
  :host {
    writing-mode: horizontal-tb;
    font-style: initial;
    font-weight: initial;
    font-variant: initial;
    line-height: initial;
    letter-spacing: initial;
    text-align: initial;
    text-indent: initial;
    text-transform: initial;
    word-spacing: initial;
    text-shadow: initial;
    cursor: initial;
  }
}

初期化の指定は少しやりすぎな気もしますが(とはいえ完璧でもないですが)、より適切な方法があれば調整していきます。

revert-layer のテクニックは以下の記事で紹介しているものですが、どのような副作用があるのかを検証する目的も兼ねて試験的に導入しています。

今回作成した Bookmarklet は、当初は基準コンテナをリサイズできる機能だけを想定しており、プライベートでの使用しか考えていなかったのですが、コードを書いていくうちに機能が充実していったので、せっかくなので公開することにしました。

まだ、ほとんど自分のサイトでしか試せていないので、使っていくうちに予測していない不具合が見つかるかもしれません。コードは今後も適宜調整していきますが、もしお気づきの点がございましたらお知らせください。

脚注

  1. 親子要素がそれぞれ inline-sizesize の場合にも子孫要素が除外されますが、まれなケースだと考えられるので、ひとまず対応は保留です。