Skip to content

Commit

Permalink
[css-conditional-5] Draft spec for scroll-state() #6402 (#10798)
Browse files Browse the repository at this point in the history
Per resolution in [1], add scroll-state() as a query function to query
sticky, snap, and overflow scroll states with a new container-type for
scroll-state queries.

[1] #6402 (comment)
  • Loading branch information
lilles authored Sep 4, 2024
1 parent 22558d5 commit 9468285
Showing 1 changed file with 232 additions and 33 deletions.
265 changes: 232 additions & 33 deletions css-conditional-5/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ Container Queries</h2>
[=container queries=] allow testing aspects of elements within the document
(such as box dimensions or computed styles).

By default, all elements are [=query containers=]
By default, all elements are <dfn export lt="query container">query containers</dfn>
for the purpose of [=container style queries=],
and can be established as [=query containers=]
for [=container size queries=] by specifying
for [=container size queries=] and [=container scroll-state queries=] by specifying
the additional query types using the 'container-type' property
(or the 'container' [=shorthand=]).
Style rules applying to a [=query container=]’s [=flat tree=] descendants
Expand Down Expand Up @@ -385,9 +385,9 @@ Container Queries</h2>
</div>

For the ''::part()'' and ''::slotted()'' <a>pseudo-element</a> selectors,
which represent real elements in the DOM tree, query containers can be
which represent real elements in the DOM tree, [=query containers=] can be
established by [=flat tree=] ancestors of those elements.
For other <a>pseudo-element</a>s, query containers can be established by
For other <a>pseudo-element</a>s, [=query containers=] can be established by
inclusive [=flat tree=] ancestors of their <a>originating element</a>.

<div class=note>
Expand Down Expand Up @@ -461,28 +461,20 @@ Creating Query Containers: the 'container-type' property</h3>

<pre class='propdef'>
Name: container-type
Value: normal | size | inline-size
Value: normal | [ [ size | inline-size ] || scroll-state ]
Initial: normal
Inherited: no
Applies to: all elements
Computed value: specified keyword
Animation type: not animatable
</pre>

The 'container-type' property establishes the element
as a <dfn export>query container</dfn>
for the purpose of [=container queries=] that require explicit containment
(such as [=container size queries=]),
allowing [=style rules=] styling its descendants
to query various aspects of its sizing and layout,
and respond accordingly.

Unless otherwise noted,
all elements are [=query containers=]
for the purpose of [=container queries=]
that do not require explicit [=containment=]
(such as [=container style queries=]),
regardless of the specified 'container-type'.
The 'container-type' property establishes the element as a
[=query container=] for certain types of queries. For size
[=container queries=], which require certain types of containment, elements
are explicitly made [=query containers=] through this property. For other
types of [=query containers=] any element can be a [=query container=], such
as for [=container style queries=].

Values have the following meanings:

Expand All @@ -503,10 +495,13 @@ Creating Query Containers: the 'container-type' property</h3>
[=style containment=],
and [=inline-size containment=]
to the [=principal box=].
<dt><dfn>scroll-state</dfn>
<dd>
Establishes a [=query container=] for [=scroll-state queries=]
<dt><dfn>normal</dfn>
<dd>
The element is not a [=query container=]
for any [=container size queries=],
for any [=container size queries=] or [=scroll-state queries=],
but remains a [=query container=] for [=container style queries=].
</dl>

Expand Down Expand Up @@ -547,6 +542,23 @@ Creating Query Containers: the 'container-type' property</h3>
</pre>
</div>

<div class=example>
Containers can also expose state that depends on scroll offset. This example
styles a descendant of a sticky positioned element when it is stuck to the
top edge:

<pre class=lang-css>
#sticky {
container-type: scroll-state;
position: sticky;
}
@container scroll-state(stuck: top) {
#sticky-child {
background-color: lime;
}
}
</pre>
</div>

<h3 id="container-name">
Naming Query Containers: the 'container-name' property</h3>
Expand Down Expand Up @@ -658,19 +670,27 @@ Container Queries: the ''@container'' rule</h3>
<pre class="prod def">
<dfn><<container-condition>></dfn> = [ <<container-name>> ]? <<container-query>>
<dfn><<container-name>></dfn> = <<custom-ident>>
<dfn><<container-query>></dfn> = not <<query-in-parens>>
| <<query-in-parens>> [ [ and <<query-in-parens>> ]* | [ or <<query-in-parens>> ]* ]
<dfn><<query-in-parens>></dfn> = ( <<container-query>> )
| ( <<size-feature>> )
| style( <<style-query>> )
| <<general-enclosed>>
<dfn><<style-query>></dfn> = not <<style-in-parens>>
| <<style-in-parens>> [ [ and <<style-in-parens>> ]* | [ or <<style-in-parens>> ]* ]
| <<style-feature>>
<dfn><<style-in-parens>></dfn> = ( <<style-query>> )
| ( <<style-feature>> )
| <<general-enclosed>>
<dfn><<container-query>></dfn> = not <<query-in-parens>>
| <<query-in-parens>> [ [ and <<query-in-parens>> ]* | [ or <<query-in-parens>> ]* ]
<dfn><<query-in-parens>></dfn> = ( <<container-query>> )
| ( <<size-feature>> )
| style( <<style-query>> )
| scroll-state( <<scroll-state-query>> )
| <<general-enclosed>>
<dfn><<style-query>></dfn> = not <<style-in-parens>>
| <<style-in-parens>> [ [ and <<style-in-parens>> ]* | [ or <<style-in-parens>> ]* ]
| <<style-feature>>
<dfn><<style-in-parens>></dfn> = ( <<style-query>> )
| ( <<style-feature>> )
| <<general-enclosed>>
<dfn><<scroll-state-query>></dfn> = not <<scroll-state-in-parens>>
| <<scroll-state-in-parens>> [ [ and <<scroll-state-in-parens>> ]* | [ or <<scroll-state-in-parens>> ]* ]
| <<scroll-state-feature>>
<dfn><<scroll-state-in-parens>></dfn> = ( <<scroll-state-query>> )
| ( <<scroll-state-feature>> )
| <<general-enclosed>>
</pre>

The keywords ''container-name/none'', ''and'', ''not'', and ''or''
Expand Down Expand Up @@ -1068,6 +1088,185 @@ Style Container Features</h3>
are [=computed value|computed=] with respect to the [=query container=],
the same as other values.

<h3 id="scroll-state-container">
Scroll State Container Features</h3>

A <dfn export>container scroll-state query</dfn> allows querying a container for
state that depends on scroll position. It is a boolean combination of
individual <dfn lt="scroll-state feature">scroll-state features</dfn>
(<<scroll-state-feature>>) that each query a single feature of the
[=query container=]. The syntax of a <dfn><<scroll-state-feature>></dfn> is the
same as for a [=media feature=]: a feature name, a comparator, and a value.

[=Scroll-state features=] can either match state of the scroller itself,
or an element that is affected by the scroll position of an ancestor
[=scroll container's=] [=scrollport=]. An example of the former is the
''overflowing'' feature, ''snapped'' the latter.

<h4 id="updating-scroll-state">
Updating Scroll State</h4>

Issue(10796): This section is subject to change as a result of resolving
the issue of unifying scroll-snapshotting layout state across several specifications.

Scroll state may cause layout cycles since queried scroll state may cause style changes,
which may lead to scroll state changes as a result of layout. The same issue exists for
[=scroll progress timelines=], and scroll state is handled in a similar manner.

To avoid such layout cycles, ''scroll-state'' [=query containers=] update their
current state once as the last step of [=run the scroll steps=]. Then, after the
resizeObserver loop in the
<a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model">HTML event loop processing model</a>,
update the current state of every ''scroll-state'' [=query container=].
If that state has changed since the scroll state update in [=run the scroll steps=],
re-run the style and layout update a single time if necessary.

<h4 id="stuck">
Sticky positioning: the '@container/stuck' feature</h4>

<pre class="descdef mq">
Name: stuck
For: @container
Value: top | right | bottom | left | block-start | inline-start | block-end | inline-end
Type: discrete
</pre>

The '@container/stuck' [=container feature=] queries whether a
''position/sticky'' positioned container is visually shifted to stay inside
the [=sticky view rectangle=] for the given edge. The logical edges map to
physical based on the direction and writing-mode of the [=query container=].
None of the values match if the [=query container=] is not [=sticky positioned=].

It is possible for two values from opposite axes to match at the same time,
but not for opposite edges along the same axis.

<div class=example>
May match:
<pre class="lang-css">
@container scroll-state((stuck: top) and (stuck: left)) { ... }
</pre>

Will never match:
<pre class="lang-css">
@container scroll-state((stuck: left) and (stuck: right)) { ... }
</pre>
</div>

In the boolean context, the query matches if visual shift is applied in any
direction.

<dl dfn-type=value dfn-for="@container/stuck">
<dt><dfn>top</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the top edge.
<dt><dfn>right</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the right edge.
<dt><dfn>bottom</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the bottom edge.
<dt><dfn>left</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the left edge.
<dt><dfn>block-start</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the [=block-start=] edge.
<dt><dfn>inline-start</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the [=inline-start=] edge.
<dt><dfn>block-end</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the [=block-end=] edge.
<dt><dfn>inline-end</dfn>
<dd>
The ''position/sticky'' container is shifted to stay inside the [=inline-end=] edge.
</dl>

<h4 id="snapped">
Scroll snapping: the '@container/snapped' feature</h4>

<pre class="descdef mq">
Name: snapped
For: @container
Value: x | y | block | inline
Type: discrete
</pre>

The '@container/snapped' [=container feature=] queries whether a [=snap target=]
is snapped to its [=snap container=] in the given axis. It matches in the boolean
context if it is snapped in at least one of the directions.

<dl dfn-type=value dfn-for="@container/snapped">
<dt><dfn>x</dfn>
<dd>
'@container/snapped' [=container feature=] matches ''x''
if the [=query container=] is a horizontal [=snap target=] for its [=scroll container=]
<dt><dfn>y</dfn>
<dd>
'@container/snapped' [=container feature=] matches ''y''
if the [=query container=] is a vertical [=snap target=] for its [=scroll container=]
<dt><dfn>block</dfn>
<dd>
'@container/snapped' [=container feature=] matches ''block''
if the [=query container=] is a [=snap target=] for its [=scroll container=]
in the block direction of the [=snap container=].
<dt><dfn>inline</dfn>
<dd>
'@container/snapped' [=container feature=] matches ''inline''
if the [=query container=] is a [=snap target=] for its [=scroll container=]
in the inline direction of the [=snap container=].
</dl>

<h4 id="overflowing">
Overflowing: the '@container/overflowing' feature</h4>

<pre class="descdef mq">
Name: overflowing
For: @container
Value: top | right | bottom | left | block-start | inline-start | block-end | inline-end
Type: discrete
</pre>

The '@container/overflowing' [=container feature=] queries whether a
[=scroll container=] has clipped [=scrollable overflow rectangle=] content
in the given direction which is reachable through user initiated scrolling.
That is, '@container/overflowing' does not match for a ''overflow/hidden''
container, nor for a [=negative scrollable overflow region=].

The logical values map to physical based on the direction and writing-mode of
the [=query container=]. None of the values match if the container is not a
[=scroll container=].

In the boolean context, the query matches if any of the values match.

<dl dfn-type=value dfn-for="@container/overflowing">
<dt><dfn>top</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the top edge.
<dt><dfn>right</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the right edge.
<dt><dfn>bottom</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the bottom edge.
<dt><dfn>left</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the left edge.
<dt><dfn>block-start</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the [=block-start=] edge.
<dt><dfn>inline-start</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the [=inline-start=] edge.
<dt><dfn>block-end</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the [=block-end=] edge.
<dt><dfn>inline-end</dfn>
<dd>
The [=scroll container=] has [=scrollable overflow=] past the [=inline-end=] edge.
</dl>


<h2 id="container-lengths">
Container Relative Lengths: the ''cqw'', ''cqh'', ''cqi'', ''cqb'', ''cqmin'', ''cqmax'' units</h2>

Expand Down

0 comments on commit 9468285

Please sign in to comment.