👀

DOMDOMタイムス#1: checkVisibility()の仕様

2023/06/12に公開

不定期でDOMやその周辺についてメモを残すDOMDOMタイムス!👶

今回はElement.prototypeに定義されているcheckVisibility()について見ていってみる。

仕様の検討状況

checkVisibility()の仕様は、CSSOM View ModuleのEditor’s draftに記載されている(https://drafts.csswg.org/cssom-view/#dom-element-checkvisibility)。Editor's draftなのであくまでも実装は各社に委ねられている状態だと言える。

いちおうW3CによるEditor’s draftの位置づけに関する説明を載せておく。

An Editor's draft is a document produced by a W3C Group.

An editor's draft is a document allowing the Group to iterate internally on its content for consideration. Editor's Drafts are works in progress inside a W3C Group and are not required to have the consensus of the Group participants. These drafts have not received formal review and are not endorsed W3C.

These drafts MUST NOT be cited as W3C standards and may or may not become W3C standards.

Software MAY implement these drafts at their own risk. Implementation is neither discouraged nor encouraged but can contribute to proposals for further action on a specification.
https://www.w3.org/standards/types#ED

各ブラウザでの実装状況

とは言え、なんだかんだ色々なブラウザで使える。いつも通りの展開。Editor's draftでも、ほぼ固まっているものはどんどん実装されていく。
Can I UseにてcheckVisibilityの実装状況を調べると、SafariとIE以外のほとんどのブラウザの最近のバージョンで使用可能なことがわかる

現時点での仕様

さてさて、ここからはCSSOM View ModuleのEditor’s draftに記述されている仕様を中心に、checkVisibilityの内容を見てみよう。

概要

基本的には「Box Treeの文脈でvisibleと言えるか」のチェックを行うとのこと。でも、optionを指定して、"opacity"や"visibility"のチェックを行うこともできるとのこと。

The [checkVisibility()](https://drafts.csswg.org/cssom-view/#dom-element-checkvisibility) method provides a set of simple checks for whether an element is potentially "visible". It defaults to a very simple and straightforward method based on the box tree, but allows for several additional checks to be opted into, depending on what precise notion of "visibility" is desired.
https://drafts.csswg.org/cssom-view/#dom-element-checkvisibility

chrome platform statusではもっと分かりやすく説明されている。

Element.checkVisibility() returns true if the element is visible, and false if it is not. It checks a variety of factors that would make an element invisible, including display:none, visibility, content-visibility, and opacity.
https://chromestatus.com/feature/5163102852087808

そして、こんなものが作られたのも、elementが見えるか見えないかという判定に色々な設定値が関係するようになってしまい、それを全て取り扱えるメソッドがあったら便利じゃないかという流れがあるよとchrome platform statusでは述べられている。

With the addition of content-visibility, there are now several different ways to hide an element. This new method accounts for all of these and can look at state script in the page can't see, such as content-visibility:hidden in the user agent shadow DOM of a closed details element.
https://chromestatus.com/feature/5163102852087808

仕様の具体に入っていくとしよう。

具体的なロジック

こんな感じで定義されているネ。順に見ていってみる。

The checkVisibility(options) method must run these steps, when called on an element this:

  1. If this does not have an associated box, return false.
  2. If a shadow-including ancestor of this has content-visibility: hidden, return false.
  3. If the [checkOpacity](https://drafts.csswg.org/cssom-view/#dom-checkvisibilityoptions-checkopacity) dictionary member of options is true, and this, or a shadow-including ancestor of this, has a computed opacity value of 0, return false.
  4. If the [checkVisibilityCSS](https://drafts.csswg.org/cssom-view/#dom-checkvisibilityoptions-checkvisibilitycss) dictionary member of options is true, and this is invisible, return false.
  5. Return true.
    https://drafts.csswg.org/cssom-view/#dom-element-checkvisibility

ステップ1

If this does not have an associated box, return false.

要するに、その要素がdisplay:contents/none のときおよびその要素のshadow rootをまたいでもいいので親要素のいずれかがdisplay:noneのとき、falseで返るということに等しい。

まず、boxというのはCSS Display Moduleのbox treeシステムでいうところのboxのこと。めちゃくちゃざっくり言うと、レイアウトの計算時に各要素に対応させられる「箱」。boxは下記のルールを持つ。

  • 要素のdisplayプロパティにしたがって、0個以上のboxが各要素に対して作られる
  • 基本的にはprincipal boxという自身のcontentや子要素に対応するbox群を包含するboxが各要素に対して1つ作られるが、下記の事情がある
    • display: none / contents だと作られない
    • display: list-item だとmarkerのboxが作られる

このルールはCSS Display ModuleのLevel3Level4(editor’s draft)のdisplayの説明から読み取れる。

checkVisibilityのステップ1では、要素がboxを持っていないことを判定しているが、boxの生成を妨げるのはdisplayプロパティのnone/contentsの2値。より正確にはそれぞれ下記のように機能する。

  • contentsはその値が設定された要素そのもののboxの生成を妨げ、子要素のbox生成に影響しない
  • noneは設定要素含めたsubtree全体に影響する

よってステップ1は要約すれば先ほど書いたような話になる。
要するに、その要素がdisplay:contents/none のときおよびその要素のshadow rootまたいでもいいので親要素のいずれかがdisplay:noneのとき、falseで返るということに等しい。

ステップ2

If a shadow-including ancestor of this has content-visibility: hidden, return false.

これは本当に書いてある通りで、shadow rootまたいでもいいので親要素のいずれかがcontent-visibility: hiddenならfalseで返るということ。

まず、shadow-including ancestorというのは要するにshadow rootをまたいだ場合も含めた祖先要素のこと。whatwgの定義が下記。

An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.
https://dom.spec.whatwg.org/#concept-shadow-including-ancestor

An object A is a shadow-including descendant of an object B, if A is a descendant of B, or A’s root is a shadow root and A’s root’s host is a shadow-including inclusive descendant of B.
https://dom.spec.whatwg.org/#concept-shadow-including-descendant

またcontent-visibilityについては、CSS Containment Module Level2/3によれば、その要素も子要素も(6/25訂正)子要素をレンダリング処理から除外する(レイアウト計算も描画もやらない)ためのプロパティ。

The content-visibility property controls whether or not an element renders its contents at all, along with forcing a strong set of containments, allowing user agents to potentially omit large swathes of layout and rendering work until it becomes needed. It has the following values:
https://www.w3.org/TR/css-contain-3/#content-visibility

一応Level2へのリンクも載せておく。
https://drafts.csswg.org/css-contain-2/#content-visibility

ステップ3

If the [checkOpacity](https://drafts.csswg.org/cssom-view/#dom-checkvisibilityoptions-checkopacity) dictionary member of options is true, and this, or a shadow-including ancestor of this, has a computed opacity value of 0, return false.

要するに、渡したoptionのcheckOpacityがtrueなら、shadow rootまたいでもいいので親要素のいずれかでopacityが0になっているときfalseで返るということに等しい。

ここでわざわざ「その要素のopacityプロパティが0に設定されている」と書かずに、"has a computed opacity value of 0"と書くのは、opacityをある要素に設定したらsubtree全体に適用されるという事情によるのだろう。

ところで、そもそもopacityはなぜ強制的にsubtree全体に適用されるかというと、opacityの処理は"a single offscreen image"として処理されるためである。これが概念的な話なのか実装としての話なのかは自分は分かっていないが、それゆえにopacityが設定されたtree全体にはそのtreeに属さない要素を視覚的に挟み込むことはできないし、opacityをつけるとstacking contextが作成される理由はまさにここにある

Since an element with opacity less than 1 is composited from a single offscreen image, content outside of it cannot be layered in z-order between pieces of content inside of it. For the same reason, implementations must create a new stacking context for any element with opacity less than 1.
https://www.w3.org/TR/css-color-3/#transparency

ステップ4

If the [checkVisibilityCSS](https://drafts.csswg.org/cssom-view/#dom-checkvisibilityoptions-checkvisibilitycss) dictionary member of options is true, and this is invisible, return false.

要するに、渡したoptionのcheckVisibilityCSSがtrueなら、その要素のvisibilityがhiddenになっているもしくはtableとflexの文脈以外でcollapseになっているとき、falseで返る。

ここに登場する"invisible"という単語は、リンク先を見るとわかるが、CSSのvisibilityプロパティにより規定される性質としての"invisible"である。

ただし「"invisible"ってvisibility:hiddenが設定されているときのことでしょ」とは単純に言えないのが面白いところ。このvisibilityプロパティのw3cによる説明は案外分かりやすいので、この際、全体を見てみよう👶

CSS Display Module Level3/4に大きな違いはなさそうなので、ここではLevel4をみていく。

いちおうLevel3へのリンクも載せておく。https://www.w3.org/TR/css-display-3/#visibility

よって、ある要素がinvisibleであるというのは、その要素のvisibilityがhiddenになっているもしくはtableとflexの文脈以外でcollapseになっているときだと言える。

思ったこと

厳密な判定が必要な場所で使おうと思うと、けっこう難しいかもなと思った。

例えば、可視性をどう考えるかに依るが、親要素にoverflow:hiddenが設定されている場合に位置関係によって見えなくなっている子要素なんかはcheckVisibilityでtrueが返ってくる。
また、他の要素に上から覆われてしまって「見えていない」状態にあるような場合もcheckVisibilityは特に考慮しないのでtrueが返るわけだ。

でも、そのあたりを理解して使うならめちゃくちゃ便利そうだヨネ。今回はここまで!

GitHubで編集を提案

Discussion