Title: CSS Scroll Snapping Change Proposal
Shortname: css-scroll-snap
Level: 1
Status: UD
Work Status: exploring
Group: CSSWG
ED: https://drafts.csswg.org/css-scroll-snap/
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/
Editor: fantasai, Invited Expert, http://fantasai.inkedblade.net/contact
Abstract: A brief description of an alternate model for scroll-snapping.
spec: css-shapes-1; type: value; for:
text: border-box
text: margin-box
Introduction {#intro}
=====================
We think scroll snapping is a great idea, and fully support exposing this functionality through CSS. However, a major weakness of the current spec is the way it conceives snapping on a coordinate model rather than a box model. This requires a lot of manual calculations in figuring out the correct coordinates from the box model; and also makes sensible scroll-snap settings dependent on the relative sizes of the viewport and the snappable contents, causing problems for users are unexpectedly large and/or small screens (a problem commonly ignored by many authors).
This proposal builds off of roc's model, using an area-snapping model to intelligently handle adaptation to multiple screen sizes. It also adds group alignment as a built-in concept, rather than requiring authors to build one in JavaScript.
Use Cases {#use-cases}
======================
Use Case 1: Snapping to the start or middle of each box
e.g. address book (start) or photo album (middle)
1. Snapping to 0.25rem above the top of each heading
:root { scroll-snap-type: proximity; }
h1, h2, h3, h4, h5, h6 {
scroll-snap-align: start;
scroll-snap-area: 0.25em;
}
2. Snapping to the center of each photo
:root { scroll-snap-type: mandatory; }
img { scroll-snap-align: center; }
Use Case 2: Snapping to the start or middle of a group of boxes,
where the number of boxes depends on how many fit in the viewport
e.g. scrolling image galleries
1. Snapping to the top of each "page" of address book entries in a list of entries
:root {
scroll-snap-type: proximity;
scroll-group-align: start;
}
article {
scroll-snap-align: group;
}
Use Case 3: Snapping to boxes (or points) in 2D
e.g. on a map, where you want to snap points of interest to the
center, or a flow-chart diagram, where you want to snap the edges
of each box into the visible area. In both cases, you don't want
objects wholly outside the visible area to influence snapping.
1. Snapping each flow chart entry to within the viewport when it falls near the edge:
:root {
scroll-snap-type: proximity;
}
li {
scroll-snap-align: edges;
}
2. Snapping each city on a map to the center of the viewport,
but only once it gets near the center in both dimensions:
:root {
scroll-snap-type: proximity;
}
.city {
scroll-snap-align: center;
}
Use Case 4: Slideshow, where successive slides are arranged horizontally,
and sometimes "detail" slides are placed below the "main" slide for that point.
<div class="slides">
<div class="slide">...</div>
<div class="slide">...</div>
<div class="slide details">
<div class="slide">...</div>
<div class="slide">...</div>
</div>
<div class="slide">...</div>
</div>
<style>
.slides {
display: flex;
flex-flow: row;
scroll-snap-type: mandatory;
overflow-x: scroll;
width: 100vw;
height: 100vh;
}
.slide {
scroll-snap-align: edges;
width: 100vw;
min-height: 100vh;
}
.slide.details {
display: flex;
flex-flow: column;
scroll-snap-type: mandatory;
overflow-y: scroll;
}
</style>
Overview of Change {#proposal}
==============================
On the scroll container:
Spec
| Proposal
| Priority
|
'scroll-snap-type'
| 'scroll-snap-type' (no change)
| High priority
|
''scroll-snap-destination: <>''
''scroll-snap-destination: auto | <>''
Keep this property only if needed for compat.
|
'scroll-snap-points-x'
| dropped
| High priority to drop. :)
|
'scroll-snap-points-y'
| dropped
| High priority to drop. :)
|
n/a
| ''scroll-group-align: [ start | end | edges ]{1,2} | <>#''
Low priority
| | | |
On the children:
Spec
| Proposal
| Priority
|
''scroll-snap-coordinate: <>#''
''scroll-snap-align: [ none | start | end | edges ]{1,2} | <> | group''
High priority; simpler version adds a center keyword to the bracketed set instead of allowing the full <># syntax inherited from 'scroll-snap-coordinate'.
''group'' keyword is low-priority.
n/a
| ''scroll-snap-area: [ border-box | margin-box ] || <>{1,4}''
High priority
| | | | |
Element-based Snapping {#element}
======================
Scroll Snapping Area: the 'scroll-snap-area' property {#scroll-snap-area}
---------------------
Name: scroll-snap-area
Value: [ border-box | margin-box ] || <>{1,4}
Initial: border-box
Applies to: all elements
Inherited: no
Computed value: as specified, with lengths made absolute
Animatable: yes, if ''border-box''/''margin-box'' are constant
Media: visual
Specifies the box that is used for snapping.
<> values give offsets (similar to 'margin' or 'border-image-outset').
Note: This functionality effectively replaces 'scroll-snap-destination',
in a way that allows for more control in mixed-content environments
(each element can specify its own offsets from the viewport edges)
and also plays better with smaller viewports (see [[#small-viewports]]).
Scroll Snapping Alignment: the 'scroll-snap-align' property {#scroll-snap-align}
--------------------------
Name: scroll-snap-align
Value: [ none | start | end | edges | center ]{1,2}
Initial: none
Applies to: all elements
Inherited: no
Computed value: as specified
Animatable: no
Media: visual
Specifies the element's snap position as an alignment of
its box within the viewport.
The first value gives alignment in the inline axis;
the second value gives alignment in the block axis.
If one value is specified, it is duplicated.
Issue: ''center'' keyword can be replaced by <># to maintain compat, if necessary.
### Snap Alignment in Small Viewports ### {#small-viewports}
The snapped position of a box is given by its scroll-snap-align property.
However,if the scroll-snap-area is larger than the viewport...
* Inertial scrolling (and scroll-adjustment caused by snapping)
continues to align to the snap-position as normal.
* For explicit/programmatic (non-fling) scrolling:
* While the area fully fills the viewport in a given axis,
snapping is ignored in that axis:
the container can be scrolled arbitrarily and will not react.
* If the container is scrolled such that the area
no longer fully fills the viewport in an axis,
the area acts as if it has both-edges snapping in that axis,
resisting outward scrolling until you fling out
or pull it sufficiently to trigger snapping to a different snap-position
(with either proximity or mandatory behavior as appropriate).
As an example, imagine a photo as the area, or a slide in a slideshow.
You want 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.
Scope of Snapping Influence {#scope}
===========================
Issue: Current spec doesn't define how to select which snap-point to snap to.
See https://lists.w3.org/Archives/Public/www-style/2015Jul/0325.html
for a proposal to ignore snap positions far outside the viewport.
Issue: UAs should be encouraged to ignore snap positions that require scrolling in two dimensions
when a one-dimensional scroll is triggered.
Issue: Define that snap-point selection is based on the final scroll position that the scroll physics would land the scroller in after a fling.
Group-based Snapping {#group}
========================
Collects all snapping areas of group-snapped boxes,
segments them into groups that will fit within the viewport,
then creates snap areas that capture
the specified alignment of each such group.
(Note that such areas may overlap,
if group-snapped boxes are arranged in an overlapping pattern.)
This is a simple form of "scrolling by pages".
Use Case 1: Snapping to the top of each "page" of address book entries in a list of entries.
:root {
scroll-snap-type: proximity;
scroll-group-align: start;
}
article {
scroll-snap-align: group;
}
Use Case 2: Scrolling an article to the first paragraph that hasn't been completely read.
article {
scroll-snap-type: proximity;
scroll-group-align: start;
}
article > * {
scroll-snap-align: group;
}
Use Case 3: Scrolling image gallery, a la Pinterest, where images are packed tightly on the page.
.gallery {
scroll-snap-type: proximity;
scroll-group-align: center;
}
.gallery > img {
scroll-snap-align: group;
}
Turning On Group Snapping: the ''group'' value of 'scroll-snap-align' {#scroll-snap-align-group}
-------------------------
Name: scroll-snap-align
New values: group
The group value
specifies that this element's scroll snap area should be group-aligned to the viewport.
Aligning the Group: the 'scroll-snap-group' property {#scroll-snap-group}
-----------------
Name: scroll-snap-group
Value: [ start | end | edges | center ]{1,2}
Initial: start
Applies to: all elements
Inherited: no
Computed value: as specified
Animatable: no
Media: visual
Specifies the alignment of a group-snapped group's area within the viewport.