Skip to content

[css-conditional-5] Draft spec for scroll-state() #6402 #10798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 232 additions & 33 deletions css-conditional-5/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,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 @@ -384,9 +384,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 @@ -460,28 +460,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 @@ -502,10 +494,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 @@ -546,6 +541,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 @@ -657,19 +669,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 @@ -1067,6 +1087,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