1. Introduction
This section is not normative.
This is currently a draft spec over Scroll Snap 1.
Scroll experiences don’t always start at the beginning. Interactions with carousels, swipe controls, and listviews often start somewhere in the middle, and each require JavaScript to set this position on page load. By enabling CSS to specify this scroll start position, both users, page authors and browsers benefit.
In addition to setting an initial scroll position, developers need insights and events into Scroll Snap. Events like which element is snapped on which axis, when the snap event is changing, when snap completes and conveniences for snapping to children programmatically.
1.1. First Layout
This event should follow the Animation code path. When animation objects are created and fire events, this is when a box has it’s first layout.
2. Motivating Examples
.carousel{ overflow-inline : auto; scroll-start : center; }
< div class = "carousel" > < img src = "img1.jpg" > < img src = "img2.jpg" > < img src = "img3.jpg" > < img src = "img4.jpg" > < img src = "img5.jpg" > </ div >
.scrollport{ overflow-block : auto; } main{ scroll-start-target : auto; }
< div class = "scrollport" > < nav > ...</ nav > < main > ...</ main > </ div >
3. Setting Where Scroll Starts
3.1. The scroll-start property
Name: | scroll-start |
---|---|
Value: | [ auto | start | end | center | left | right | top | bottom | <length-percentage [0,∞]> ]{1,2} |
Initial: | see individual properties |
Applies to: | see individual properties |
Inherited: | see individual properties |
Percentages: | see individual properties |
Computed value: | see individual properties |
Animation type: | see individual properties |
Canonical order: | per grammar |
This property is a shorthand property that sets all of the scroll-start-* longhands in one declaration. The first value defines the scroll starting point in the block axis, the second sets it in the inline axis. If the second value is omitted, it defaults to start. If scroll-start-target is set on any child, scroll-start is not used, in favor of using the element as the offset.
Values are defined as follows:
- auto
- ...
- <length-percentage [0,∞]>
-
...
Negative values are invalid. Values corresponding to a length greater than the width/height of the scrollport are valid, but clamped to the width/height of the scrollport.
- start
- center
- end
- center
- Equivalent to 0%, 50%, and 100%, respectively.
3.1.1. Interaction with display: none and initial creation
Same behavior that animations follow with first layout.3.1.2. Slow page loading or document streaming behavior
TODO3.1.3. Interaction with fragment navigation
TODO If the scrollport has a in-page :target via a URL fragment or a previous scroll position, then scroll-start is unused. Existing target logic should go unchanged. This makes scroll-start a soft request in the scroll position resolution routines.3.1.4. Interaction with place-content
TODO Side note: While place-content can make a scroller appear to start in the center or end, no browser supports it and it appears complicated to resolve.3.1.5. Interaction with "find in page"
TODO3.1.6. Interaction scroll-snap containers
This effectively will layout and start scroll at the snapped child, thus negating / cancelling scroll-start. scroll-start will only work if nothing else has effected the scroll position.3.1.7. Nested scrollers with scroll-start
Should follow patterns that scroll snap has set.3.1.8. Interaction when display is toggled
Same behavior that animations follow with first layout.3.1.9. Interaction with RTL and LTR
Logical properties are offered for length offsets that should be flow relative. Furthermore, the end and start keywords are always logical.3.2. The scroll-start-target property
Name: | scroll-start-target |
---|---|
Value: | [ none | auto ]{1,2} |
Initial: | see individual properties |
Applies to: | see individual properties |
Inherited: | see individual properties |
Percentages: | see individual properties |
Computed value: | see individual properties |
Animation type: | see individual properties |
Canonical order: | per grammar |
This property is a shorthand property that sets all of the scroll-start-target-* longhands in one declaration. The first value defines the scroll starting point in the block axis, the second sets it in the inline axis. If the second value is omitted, it defaults to none.
Values are defined as follows:
- none
- Element is not a scroll-start-target.
- auto
- Element is used to calculate the scroll-start position, taking into account scroll-padding or scroll-margin , same as a scroll-snap target.
4. Styling Snapped Items
4.1. The Snapped-element Pseudo-class: :snapped
The :snapped pseudo-class matches any scroll snap targets, regardless of axis. The longform physical and logical pseudo-class selectors allow for more finite snapped children styling as they can target an individual axis.
More specific options are defined as follows:
- :snapped-x
- Matches the child snapped on the horizontal axis.
- :snapped-y
- Matches the child snapped on the vertical axis.
- :snapped-inline
- Matches the child snapped on the inline axis.
- :snapped-block
- Matches the child snapped on the block axis.
Note: Issue #6985
Need to figure out resolution of the initial frame.
5. Snap Events
5.1. scrollsnapchange
and scrollsnapchanging
CSS scroll snap points are often used as a mechanism to create scroll interactive "selection" components, where selection is determined with JavaScript intersection observers and a scroll end guestimate. By creating a built-in event, the invisible state will become actionable, at the right time, and always correct.
Event | Interface | Targets | Description |
---|---|---|---|
scrollsnapchange
| SnapEvent
| scroll containers | Fired at the scrolling element or Document at the end of a scroll (before a scrollend event)
or after a layout snap if the element that the scrolling element or Document is snapped to changed.
|
scrollsnapchanging
| SnapEvent
| scroll containers | Fired at the scrolling element or Document during scrolling (before a scroll event),
if the element that the scrolling would cause the scroller to snap to is
different from the target reported by the last scrollsnapchanging event that was fired.
|
5.1.1. scrollsnapchange
scrollsnapchange
indicates that the snap area to which a snap container is snapped along either axis has changed. scrollsnapchange
is dispatched:
- when a scrolling operation is completed if, for either the block or inline axis, the element which the snap container is snapped to is different from the element it most recently snapped to in that axis. For snap containers with proximity strictness, the scroll may result in the snap container no longer being snapped to any element. CSS Scroll Snap 1 § 6.2 Choosing Snap Positions describes the method a UA follows when choosing between elements which are snap areas.
- if there is a change to a snap container’s style such that it goes from having a non-none value for scroll-snap-type to having a none value or vice versa.
- if, after a layout change, the element to which a snap container is snapped to changes, regardless of whether there is a change in scroll position after the layout change.
Scrolling operations always lead to scrollend
events being fired. If a
scrolling operation also results in a scrollsnapchange
event being fired, the scrollsnapchange
event should be fired before the scrollend
event.
Each Document
has an associated list of pending scrollsnapchange event targets, initially empty.
Each snap container has
one scrollsnapchangeTargetBlock and one scrollsnapchangeTargetInline in the block and inline axes
respectively, each of which can either be null if the container is not
snapped in that axis or the Element
to which the container is snapped.
When asked to update scrollsnapchange targets for a snap container, snapcontainer, run these steps:
-
Let doc be snapcontainer’s associated
Document
. -
Let blockTarget be the scrollsnapchangeTargetBlock associated with snapcontainer.
-
Let inlineTarget be the scrollsnapchangeTargetInline associated with snapcontainer.
-
Let blockScrollSnapchangingTarget be the scrollsnapchangingTargetBlock associated with snapcontainer.
-
Let inlineScrollSnapchangingTarget be the scrollsnapchangingTargetInline associated with snapcontainer.
-
Let snap targets changed be a boolean flag that is initially false.
-
If blockTarget is not the same element as blockScrollSnapchangingTarget or
-
Set the scrollsnapchangeTargetBlock associated with snapcontainer to blockScrollSnapchangingTarget.
-
Set snap targets changed to true.
-
-
If inlineTarget is not the same element as inlineScrollSnapchangingTarget:
-
Set the scrollsnapchangeTargetInline associated with snapcontainer to inlineScrollSnapchangingTarget.
-
Set snap targets changed to true.
-
-
If snap targets changed is true:
-
If snapcontainer is not already in doc’s pending scrollsnapchange event targets:
-
Append snapcontainer to doc’s pending scrollsnapchange event targets.
-
-
Note: When snapping occurs on a scroller (either due to a layout change or a
scrolling operation) the scrollsnapchangingTargetBlock and scrollsnapchangingTargetInline associated with that scroller are updated and represent the current snap targets
of that scroller. This allows the update scrollsnapchange targets algorithm
to use these elements to determine whether a scrollsnapchange
event should be fired.
When asked to dispatch pending scrollsnapchange events for a Document
, doc, run these steps:
-
For each item target in doc’s pending scrollsnapchange event targets:
-
Fire a
SnapEvent
, snapevent, namedscrollsnapchange
at target and let snapevent’ssnapTargetBlock
andsnapTargetInline
attributes be the scrollsnapchangeTargetBlock and the scrollsnapchangeTargetInline, respectively, that are associated with target.
-
-
Empty doc’s pending scrollsnapchange event targets.
5.1.2. scrollsnapchanging
scrollsnapchanging
is dispatched:
-
during a scrolling operation, if the element to which a snap container would snap (in either axis) changes, or
-
if a layout change occurs such that a
scrollsnapchange
event is to be dispatched. In this case, as with the scrolling case, thescrollsnapchanging
event should be dispatched before thescrollsnapchange
event.
A scrolling operation might animate towards a particular position (e.g. scrollbar arrow clicks, arrow key presses, "behavior: smooth" programmatic scrolls) or might directly track a user’s input (e.g. touch scrolling, scrollbar dragging). In either case, the user agent chooses an eventual snap target in each axis to which the scroller will snap after the scrolling operation reaches its intended scroll position.
-
In the former case, the intended scroll position is the scroll animation’s target scroll offset.
-
In the latter case, the intended scroll position is the current scroll offset as determined by the user’s input.
scrollsnapchanging
aims to let the web page know, as early as possible,
that the scrolling operation will result in a change in the element the snap
container is snapped to. The user agent should evaluate whether to trigger scrollsnapchanging
based on the eventual snap target to which the scroller would snap were the scrolling operation
to reach its intended scroll position.
Note: Since scrollsnapchanging gives the web page hints about future snapping, the snapping hinted at by a scrollsnapchanging event might not materialize since it will be possible for subsequent scrolling input to further alter the snap container’s scroll position and result in a different eventual snap target.
scrollsnapchanging
events are fired before scroll
events.
Each Document
has an associated list of pending scrollsnapchanging event targets, initially empty.
Each snap container has
one scrollsnapchangingTargetBlock and one scrollsnapchangingTargetInline in the block and inline axes
respectively, each of which can either be null if the container is not
snapping in that axis or the Element
to which the container is snapping.
When asked to update scrollsnapchanging targets for a snap container, snapcontainer, given an Element
newBlockTarget and an Element
newInlineTarget, run these steps:
-
Let doc be snapcontainer’s associated
Document
. -
Let blockTarget be the scrollsnapchangingTargetBlock that is associated with snapcontainer.
-
Let inlineTarget be the scrollsnapchangingTargetInline that is associated with snapcontainer.
-
If newBlockTarget is not the same element as blockTarget:
-
Set the scrollsnapchangingTargetBlock associated with snapcontainer to newBlockTarget.
-
If snapcontainer is not already in doc’s pending scrollsnapchanging event targets,
-
Append snapcontainer to doc’s pending scrollsnapchanging event targets
-
-
-
If newInlineTarget is not the same element as inlineTarget:
-
Set the scrollsnapchangingTargetInline associated with snapcontainer to newInlineTarget.
-
If snapcontainer is not already in doc’s pending scrollsnapchanging event targets,
-
Append snapcontainer to doc’s pending scrollsnapchanging event targets.
-
-
When asked to dispatch pending scrollsnapchanging events for a Document
, doc, run these steps:
-
For each item target in doc’s pending scrollsnapchanging event targets:
-
Fire a
SnapEvent
, snapevent, namedscrollsnapchanging
at target and let snapevent’ssnapTargetBlock
andsnapTargetInline
attributes be the scrollsnapchangingTargetBlock and the scrollsnapchangingTargetInline, respectively, that are associated with target.
-
-
Empty doc’s pending scrollsnapchanging event targets.
5.1.3. Snap Events due to Layout Changes
When a snap container, snapcontainer, re-snaps, run these steps:-
Let newBlockTarget be the element that snapcontainer has snapped to in the block axis or null if it did not snap to any element.
-
Let newInlineTarget be the element that snapcontainer has snapped to in the inline axis or null if it did not snap to any element.
-
Run the steps to update scrollsnapchanging targets with newBlockTarget as newBlockTarget and newInlineTarget as newInlineTarget.
-
Run the steps to update scrollsnapchange targets for snapcontainer.
5.2. SnapEvent interface
dictionary :
SnapEventInit EventInit {Node ?;
snapTargetBlock Node ?; }; [
snapTargetInline Exposed =Window ]interface :
SnapEvent Event {(
constructor DOMString ,
type optional SnapEventInit = {});
eventInitDict readonly attribute Node ?snapTargetBlock ;readonly attribute Node ?snapTargetInline ; };
snapTargetBlock
, of type Node, readonly, nullable-
The element that the snap container is snapped to in the block axis at the snap position for the associated snap event.
snapTargetInline
, of type Node, readonly, nullable-
The element that the snap container is snapped to in the inline axis at the snap position for the associated snap event.
For scrollsnapchange
events, the snap position is the position already
realized by the snap container after a scroll snap. For scrollsnapchanging
events it is the snap position that the snap container will eventually
snap to when the scrolling operation ends.
A SnapEvent
should not bubble and should not be cancellable.
Appendix A: Longhands
The physical and logical longhands (and their shorthands) interact as defined in [CSS-LOGICAL-1].
Physical Longhands for scroll-start
Name: | scroll-start-x, scroll-start-y |
---|---|
Value: | auto | start | end | center | <length-percentage [0,∞]> |
Initial: | auto |
Applies to: | scroll containers |
Inherited: | no |
Percentages: | relative to the corresponding axis of the scroll container’s scrollport |
Computed value: | the keyword auto or a computed <length-percentage> value |
Canonical order: | per grammar |
Animation type: | by computed value type |
Logical property group: | scroll-start |
...
Flow-relative Longhands for scroll-start
Name: | scroll-start-inline, scroll-start-block |
---|---|
Value: | auto | start | end | center | <length-percentage [0,∞]> |
Initial: | auto |
Applies to: | scroll containers |
Inherited: | no |
Percentages: | relative to the corresponding axis of the scroll container’s scrollport |
Computed value: | the keyword auto or a computed <length-percentage> value |
Canonical order: | per grammar |
Animation type: | by computed value type |
Logical property group: | scroll-start |
...
Flow-relative Longhands for scroll-start-target
Name: | scroll-start-target-block, scroll-start-target-inline |
---|---|
Value: | auto | none |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | either of the keywords "none" or "auto" |
Canonical order: | per grammar |
Animation type: | not animatable |
Logical property group: | scroll-start-target |
...
Physical Longhands for scroll-start-target
Name: | scroll-start-target-x, scroll-start-target-y |
---|---|
Value: | none | auto |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | either of the keywords "none" or "auto" |
Canonical order: | per grammar |
Animation type: | not animatable |
Logical property group: | scroll-start-target |
...
Appendix B: Event Handlers
This section should be moved to the HTML event handler specification.
Event handlers on elements, Document objects and Window objects
The following are additional event handlers (and their corresponding event handler event types)
that must be supported by all HTML elements as both event handler content attributes and event handler IDL attributes; and that must be supported by all Window
objects and Document
objects, as event handler IDL attributes:
Event handler | Event handler event type |
---|---|
onsnapchanged
| snapchanged
|
onsnapchanging
| snapchanging
|
Extensions to the GlobalEventHandlers
Interface Mixin
This specification extends the GlobalEventHandlers
interface mixin from
HTML to add event handler IDL attributes for SnapEvents
as defined
in Event handlers on elements, Document objects and Window objects.
IDL Definition
partial interface mixin GlobalEventHandlers {attribute EventHandler ;
onsnapchanged attribute EventHandler ; };
onsnapchanging