FFFF csswg-drafts/css-scroll-snap/Overview.bs at c7ca2e5cf82ddb73a8e58484eeee21750fd32d2e · w3c/csswg-drafts · GitHub
Skip to content

Latest commit

 

History

History
1036 lines (889 loc) · 46.9 KB

File metadata and controls

1036 lines (889 loc) · 46.9 KB
-->
Scroll Snapping Area: the 'scroll-snap-margin' property {#scroll-snap-margin}
-----------------------------------------------------------------------------
<pre class="propdef">
Name: scroll-snap-margin
Value: <<length>>{1,4}
Initial: 0
Applies to: all elements
Inherited: no
Percentages: n/a
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length
</pre>
The 'scroll-snap-margin' property defines
the <dfn lt="scroll snap area" local-lt="snap area">scroll snap area</dfn>
that is used for snapping this box to the snapport.
The <<length>> values give outsets
(interpreted as for 'margin' or 'border-image-outset').
The <a>scroll snap area</a> is the rectangular bounding box of the transformed border box,
plus the specified outsets,
axis-aligned in the <a>scroll container’s</a> coordinate space.
Note: This ensures that the <a>scroll snap area</a> is always rectangular
and axis-aligned to the <a>scroll container’s</a> coordinate space.
This property is a <a>shorthand property</a> that sets
all of the <a href="#longhands"><css>scroll-snap-margin-*</css> longhands</a>
in one declaration.
<!--
███ ██ ████ ██████ ██ ██
██ ██ ██ ██ ██ ██ ███ ██
██ ██ ██ ██ ██ ████ ██
██ ██ ██ ██ ██ ████ ██ ██ ██
█████████ ██ ██ ██ ██ ██ ████
██ ██ ██ ██ ██ ██ ██ ███
██ ██ ████████ ████ ██████ ██ ██
-->
Scroll Snapping Alignment: the 'scroll-snap-align' property {#scroll-snap-align}
--------------------------------------------------------------------------------
<pre class="propdef">
Name: scroll-snap-align
Value: [ none | start | end | center ]{1,2}
Initial: none
Applies to: all elements
Inherited: no
Percentages: n/a
Media: interactive
Computed value: two keywords
Animatable: no
</pre>
The 'scroll-snap-align' property specifies
the box’s <a>snap position</a> as an alignment of
its <a>snap area</a> (as the <a>alignment subject</a>)
within its <a>snap container’s</a> <a>snapport</a> (as the <a>alignment container</a>).
The two values specify the snapping alignment
in the <a>inline axis</a> and <a>block axis</a>, respectively.
If only one value is specified, the second value defaults to the same value.
Values are defined as follows:
<dl dfn-type="value" dfn-for="scroll-snap-align">
<dt><dfn>none</dfn>
C054 <dd>
This box does not define a <a>snap position</a> in the specified axis.
<dt><dfn>start</dfn>
<dd>
Start alignment of this box’s <a>scroll snap area</a>
within the <a>scroll container</a>’s <a>snapport</a>
is a <a>snap position</a>
in the specified axis.
<dt><dfn>end</dfn>
<dd>
End alignment of this box’s <a>scroll snap area</a>
within the <a>scroll container</a>’s <a>snapport</a>
is a <a>snap position</a>
in the specified axis.
<dt><dfn>center</dfn>
<dd>
Center alignment of this box’s <a>scroll snap area</a>
within the <a>scroll container</a>’s <a>snapport</a>
is a <a>snap position</a>
in the specified axis.
</dl>
<h4 id="snap-scope">
Scoping Valid Snap Positions to Visible Boxes</h4>
Since the purpose of scroll snapping is to align content within the viewport
for optimal viewing,
a <a>scroll container</a> cannot be <a>snapped</a> to a <a>snap area</a>
if no part of it is within the <a>snapport</a>.
For example, a <a>snap area</a> is top-aligned to the <a>snapport</a>
if its top edge is coincident with the <a>snapport</a>’s top edge;
however, this scroll position is nonetheless not a valid <a>snap position</a>
if the entire <a>snap area</a> is outside the <a>snapport</a>
(because the required alignment, though satisfied, would not be relevant to the viewer).
<figure>
<pre class="ascii-art">
╔════viewport════╗┈┈┈┈┈┈┈┈┌──────────────┐
║ ┌─────┐ ┌──┐ ║ │ top-snapping │
║ ├──┐ │ └──┘ ║ │ element │
║ └──┴──┘ ║ │ │
╚════════════════╝ │ │
└──────────────┘
</pre>
<figcaption>
Alignment of an off-screen element
is not considered <a>snapping</a>.
</figcaption>
</figure>
<details class="note">
<summary>Why limit snapping to only when the element is visible?</summary>
As the <a href="https://www.webkit.org/blog/4017/scroll-snapping-with-css-snap-points/">WebKit implementers point out</a>,
extending a snap edge infinitely across the canvas
only allows for snapping gridded layouts,
and produces odd behavior for the user
when off-screen elements do not align
with on-screen elements.
(If this requirement is onerous for implementers however,
we can default to a gridded behavior
and introduce a switch to get smarter behavior later.)
</details>
</div>
<h4 id="snap-overflow">
Snapping Boxes that Overflow the Scrollport</h4>
If the <a>snap area</a> is larger than the <a>snapport</a> in a particular axis,
then any scroll position in which the <a>snap area</a> covers the <a>snapport</a>,
and the distance between the geometrically previous and subsequent <a>snap positions</a> in that axis
is larger than size of the <a>snapport</a> in that axis,
is a valid <a>snap position</a> in that axis.
The UA may use the specified alignment as a more precise target
for certain scroll operations (e.g. explicit paging).
<div class="example">
For example, take the first example in [[#examples]],
which had a photo as the area.
The author wants mandatory snapping from item to item,
but if the item happens to be larger than your viewport,
you want to be able to scroll around the whole thing once you’re over it.
Since the <a>snap area</a> is larger than the <a>snapport</a>,
while the area fully fills the viewport,
the container can be scrolled arbitrarily
and will not try to snap back to its aligned position.
However, if the container is scrolled such that the area
no longer fully fills the viewport in an axis,
the area resists outward scrolling
until it is scrolled sufficiently
to trigger snapping to a different <a>snap position</a>.
</div>
<div class=example>
For another example,
mandatory top-snapping on nested <{section}> elements
can produce large snapping areas
(from large top-level sections)
potentially filled with smaller snapping areas
(from the subsections).
When the subsections are small enough,
they snap normally;
when they're longer,
the viewer can scroll arbitrarily within them,
or within a large segment of the top-level section that has no subsections to snap to.
<figure>
<pre class=ascii-art>
┌─ top-level section ─┐ ━┓
│ │ 1┃
│ │ ┃
│ │ ━┩
│ │ ┆
│ │ ┆
│┌─── sub-section ───┐│ ╯ ━┓
│└───────────────────┘│ 2┃
│┌─── sub-section ───┐│ ━┓ ┃
││ ││ 3┃ ━┛
│└───────────────────┘│ ┃
│┌─── sub-section ───┐│ ━┛ ━┓
│└───────────────────┘│ 4┃
│┌─── sub-section ───┐│ ━┓ ┃
││ ││ 5┃ ━┛
││ ││ ┃
││ ││ ━┩
││ ││ ┆
││ ││ ┆
││ ││ ┆
│└───────────────────┘│ ┆
└─────────────────────┘ ╯
</pre>
<figcaption>
In the figure above,
the five numbered viewports
represent the five snap positions
associated with the top-level section
and its four subsections.
Because the first and last snap positions are part of ranges taller than the viewport,
the viewer is allowed to scroll freely
between the top and bottom of each range.
</figcaption>
</figure>
Note: If the author had instead set mandatory snap positions
on the headings of each section
(rather than the sections themselves),
the contents of the first and fifth sections
would be partially inaccessible to the user,
as the heading snap area does not extend to cover the whole section.
</div>
<h4 id="unreachable">
Unreachable Snap Positions</h4>
If a <a>snap position</a> is unreachable as specified,
such that aligning to it would require scrolling the <a>scroll container</a>’s viewport
past the edge of its <a>scrollable overflow region</a>,
the <em>used</em> <a>snap position</a> for this <a>snap area</a>
is the position resulting from scrolling <em>as much as possible</em>
in each relevant axis
toward the desired <a>snap position</a>.
<!--
██████ ████████ ███████ ████████
██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██
██████ ██ ██ ██ ████████
██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██
██████ ██ ███████ ██
-->
Scroll Snap Limits: the 'scroll-snap-stop' property {#scroll-snap-stop}
--------------------------
<pre class="propdef">
Name: scroll-snap-stop
Value: normal | always
Initial: normal
Applies to: all elements
Inherited: no
Percentages: n/a
Computed value: as specified
Animatable: no
Media: interactive
</pre>
This property specifies whether the <a>scroll container</a>
must stop at each <a>snap position</a> it passes,
or may pass multiple <a>snap positions</a> before coming to rest.
Values are defined as follows:
<dl dfn-type=value dfn-for=scroll-snap-stop>
<dt><dfn>normal</dfn>
<dd>
The <a>scroll container</a> may pass by a <a>snap position</a> defined
by this element during the execution of a scrolling operation.
<dt><dfn>always</dfn>
<dd>
The <a>scroll container</a> must not pass by a <a>snap position</a>
defined by this element during the execution of a scrolling operation.
</dl>
Snapping Mechanics {#snap-concepts}
===================================
The precise model algorithm to select a <a>snap position</a> to snap to
is intentionally left mostly undefined,
so that user agents can take into account sophisticated models of user intention and interaction
and adjust how they respond over time,
to best serve the user.
This section defines some useful concepts to aid in discussing scroll-snapping mechanics,
and provides some guidelines for what an effective scroll-snapping strategy might look like.
User agents are encouraged to adapt this guidance
and apply their own best judgement
when defining their own snapping behavior.
It also provides a small number of behavior requirements,
to ensure a minimum reasonable behavior that authors can depend on
when designing their interfaces with scroll-snapping in mind.
<!--
████████ ██ ██ ████████ ████████ ██████
██ ██ ██ ██ ██ ██ ██ ██
██ ████ ██ ██ ██ ██
██ ██ ████████ ██████ ██████
██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██
██ ██ ██ ████████ ██████
-->
Types of Scrolling Methods {#scroll-types}
------------------------------------------
When a page is scrolled,
the action is performed with
an intended end position
and/or an intended direction.
Each combination of these two things
defines a distinct category of scrolling,
which can be treated slightly differently:
: <dfn export>intended end position</dfn>
::
<div class="example">
Common examples of scrolls with only an <a>intended end position</a> include:
34B2 * a panning gesture,
released without momentum
* manipulating the scrollbar “thumb” explicitly
* programmatically scrolling via APIs such as {{Window/scrollTo()}}
* tabbing through the document’s focusable elements
* navigating to an anchor within the page
</div>
: <dfn export>intended direction and end position</dfn>
::
<div class="example">
Common examples of scrolls with both an <a>intended direction and end position</a> include:
* a “fling” gesture,
interpreted with momentum
* programmatically scrolling via APIs such as {{Window/scrollBy()}}
</div>
The intended end point of the scroll prior to intervention from features such
as snap points is its <dfn noexport>natural end-point</dfn>.
: <dfn export>intended direction</dfn>
::
<div class="example">
Common examples of scrolls with only an <a>intended direction</a> include:
* pressing an arrow key on the keyboard
* a swiping gesture interpreted as a fixed (rather than inertial) scroll
</div>
Additionally, because page layouts usually align things vertically and/or horizontally,
UAs sometimes <dfn export>axis-lock</dfn> a scroll when its direction
is sufficiently vertical or horizontal.
An <a>axis-locked</a> scroll is bound to only scroll along that axis.
This prevents less-precise input mechanisms from drifting in the non-primary axis.
<!--
██████ ██ ██ ███████ ███████ ██████ ████ ██ ██ ██████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
██ █████████ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ ████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██
██████ ██ ██ ███████ ███████ ██████ ████ ██ ██ ██████
-->
Choosing Snap Positions {#choosing}
-----------------------------------
A <a>scroll container</a> can have many <a>snap areas</a>
scattered throughout its <a>scrollable overflow region</a>.
A naïve algorithm for selecting a <a>snap position</a>
can produce behavior that is unintuitive for users,
so care is required when designing a selection algorithm.
Here are a few pointers that can aid in the selection process:
* <a>Snap positions</a> should be chosen to minimize the distance between the end-point
(or the <a>natural end-point</a>)
and the final snapped scroll position,
subject to the additional constraints listed in this section.
* If a scroll is <a>axis-locked</a>,
any <a>snap positions</a> in the other axis should be ignored
during the scroll.
(However, <a>snap positions</a> in the other axis can still effect the final scroll position.)
* In order to prevent a far-offscreen element
from having difficult-to-understand effects
on the scroll position,
<a>snap positions</a> should be ignored if their elements are far outside of the “corridor”
that the <a>snapport</a> defines as it moves through the <a>scrollable overflow region</a>,
or a hypothetical “corridor” in the direction of a scroll with only an <a>intended direction</a>,
or the <a>snapport</a> after an scroll with only an <a>intended end position</a>.
* User agents <em>must</em> ensure that a user can “escape” a <a>snap position</a>,
regardless of the scroll method.
For example, if the snap type is ''mandatory''
and the next <a>snap position</a> is more than two screen-widths away,
a naïve “always snap to nearest” selection algorithm might “trap” the user
if their end position was only one screen-width away.
Instead, a smarter algorithm that only returned to the starting <a>snap position</a>
if the end-point was a fairly small distance from it,
and otherwise ignored the starting snap position,
would give better behavior.
(This implies that a scroll with only an <a>intended direction</a>
must always ignore the starting <a>snap positions</a>.)
* If a page is navigated to a fragment that defines a target element
(one that would be matched by '':target'',
or the target of {{Element/scrollIntoView()}}),
and that element defines some <a>snap positions</a>,
the user agent should <a>snap</a> to one of that element’s <a>snap positions</a>.
The user agent may do this even when the <a>scroll container</a> has ''scroll-snap-type: none''.
<!--
██ ███████ ██ ██ ██████ ██ ██ ███ ██ ██ ████████ ██████
██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██
██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ████ █████████ ██ ██ ██ ██ ██ ██ ██ ██████
██ ██ ██ ██ ████ ██ ██ ██ ██ █████████ ██ ████ ██ ██ ██
██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██
████████ ███████ ██ ██ ██████ ██ ██ ██ ██ ██ ██ ████████ ██████
-->
Appendix A: Longhands {#longhands}
==================================
Physical Longhands for 'scroll-padding' {#padding-longhands-physical}
----------------------------------------------------------------------
<pre class="propdef">
Name: scroll-padding-top, scroll-padding-right, scroll-padding-bottom, scroll-padding-left
Value: <<length>> | <<percentage>>
Initial: 0
Applies to: <a>scroll containers</a>
Inherited: no
Percentages: relative to the scroll container’s scrollport
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length, percentage, or calc
</pre>
These <a>longhands</a> of 'scroll-padding' specify
the top, right, bottom, and left edges
of the <a>snapport</a>, respectively.
Flow-relative Longhands for 'scroll-padding' {#padding-longhands-logical}
--------------------------------------------------------------------------
<pre class="propdef">
Name: scroll-padding-inline-start, scroll-padding-block-start, scroll-padding-inline-end, scroll-padding-block-end
Value: <<length>> | <<percentage>>
Initial: 0
Applies to: <a>scroll containers</a>
Inherited: no
Percentages: relative to the scroll container’s scrollport
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length, percentage, or calc
</pre>
These <a>longhands</a> of 'scroll-padding' specify
the block-start, inline-start, block-end, and inline-end edges
of the <a>snapport</a>, respectively.
<pre class="propdef">
Name: scroll-padding-block, scroll-padding-inline
Value: [ <<length>> | <<percentage>> ]{1,2}
Initial: 0
Applies to: all elements
Inherited: no
Percentages: relative to the scroll container’s scrollport
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length, percentage, or calc
</pre>
These <a>shorthands</a> of 'scroll-padding-block-start' + 'scroll-padding-block-end'
and 'scroll-padding-inline-start' + 'scroll-padding-inline-end'
are <a>longhands</a> of 'scroll-padding',
and specify the block-axis and inline-axis edges of the <a>snapport</a>, respectively.
If two values are specified, the first gives the start value
and the second gives the end value.
Physical Longhands for 'scroll-snap-margin' {#margin-longhands-physical}
-------------------------------------------------------------------------
<pre class="propdef">
Name: scroll-snap-margin-top, scroll-snap-margin-right, scroll-snap-margin-bottom, scroll-snap-margin-left
Value: <<length>>
Initial: 0
Applies to: all elements
Inherited: no
Percentages: n/a
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length
</pre>
These <a>longhands</a> of 'scroll-snap-margin' specify
the top, right, bottom, and left edges
of the <a>scroll snap area</a>, respectively.
Flow-relative Longhands for 'scroll-snap-margin' {#margin-longhands-logical}
-----------------------------------------------------------------------------
<pre class="propdef">
Name: scroll-snap-margin-block-start, scroll-snap-margin-inline-start, scroll-snap-margin-block-end, scroll-snap-margin-inline-end
Value: <<length>>
Initial: 0
Applies to: all elements
Inherited: no
Percentages: n/a
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length
</pre>
These <a>longhands</a> of 'scroll-snap-margin' specify the
block-start, inline-start, block-end, and inline-end edges
of the <a>scroll snap area</a>, respectively.
<pre class="propdef">
Name: scroll-snap-margin-block, scroll-snap-margin-inline
Value: <<length>>{1,2}
Initial: 0
Applies to: all elements
Inherited: no
Percentages: n/a
Media: interactive
Computed value: as specified, with lengths made absolute
Animatable: as length
</pre>
These <a>shorthands</a> of 'scroll-snap-margin-block-start' + 'scroll-snap-margin-block-end'
and 'scroll-snap-margin-inline-start' + 'scroll-snap-margin-inline-end'
are <a>longhands</a> of 'scroll-snap-margin',
and specify the block-axis and inline-axis edges
of the <a>scroll snap area</a>, respectively.