DOMDOMタイムス#1: checkVisibility()の仕様
不定期で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でも、ほぼ固まっているものはどんどん実装されていく。
現時点での仕様
さてさて、ここからは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:
- If this does not have an associated box, return false.
- If a shadow-including ancestor of this has content-visibility: hidden, return false.
- 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.- If the
[checkVisibilityCSS](https://drafts.csswg.org/cssom-view/#dom-checkvisibilityoptions-checkvisibilitycss)
dictionary member of options is true, and this is invisible, return false.- 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のLevel3やLevel4(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をみていく。
-
大前提だが、visibilityプロパティはboxをレイアウト上には残す(後述のように大きさを変化させることはあれど)。見えないようにするだけの設定値である。
The visibility property specifies whether the box is rendered. Invisible boxes still affect layout. (Set the display property to none to suppress box generation altogether.).
https://drafts.csswg.org/css-display-4/#invisibleInvisible boxes are not rendered (as if they were fully transparent), cannot be interacted with (and behave as if they had pointer-events: none), are removed from navigation (similar to display: none), and are also not rendered to speech (except when speak is always [CSS-SPEECH-1]).
https://drafts.csswg.org/css-display-4/#invisible -
また、ある要素がinvisibleでも子要素はvisibleならちゃんと見える。これはdisplay: contentsに少し似ている
However, as with display: contents, their semantic role as a container is not affected, to ensure that any visible descendants are properly interpreted.
https://drafts.csswg.org/css-display-4/#invisible -
そのうえで、プロパティ値の"visible"や"hidden"はご存知の通りの働きをするが、"collapse"だけは少し癖がある。tableとflexの文脈においてはboxを圧縮して(完全に潰しはしない)、その他の文脈においてはhiddenと同じ働きをする
collapse
Indicates that the box is collapsed, which can cause it to take up less space than otherwise in a formatting-context–specific way. See dynamic row and column effects in tables [CSS2] and collapsed flex items in flex layout [CSS-FLEXBOX-1]. In all other cases, however, (i.e. unless otherwise specified) this simply makes the box invisible, just like hidden.
いちおうLevel3へのリンクも載せておく。https://www.w3.org/TR/css-display-3/#visibility
よって、ある要素がinvisibleであるというのは、その要素のvisibilityがhiddenになっているもしくはtableとflexの文脈以外でcollapseになっているときだと言える。
思ったこと
厳密な判定が必要な場所で使おうと思うと、けっこう難しいかもなと思った。
例えば、可視性をどう考えるかに依るが、親要素にoverflow:hiddenが設定されている場合に位置関係によって見えなくなっている子要素なんかはcheckVisibilityでtrueが返ってくる。
また、他の要素に上から覆われてしまって「見えていない」状態にあるような場合もcheckVisibilityは特に考慮しないのでtrueが返るわけだ。
でも、そのあたりを理解して使うならめちゃくちゃ便利そうだヨネ。今回はここまで!
Discussion