Gecko:CSSScrollSnapping

From MozillaWiki
Revision as of 10:44, 13 December 2013 by Roc (talk | contribs) (→‎Proposal)
Jump to navigation Jump to search

Proposal

scroll-snap-type: none | proximity | mandatory (shorthand setting both x and y)

scroll-snap-type-y: none | proximity | mandatory

scroll-snap-type-x: none | proximity | mandatory

These properties apply to scrollable elements. Values are defined similar to the Microsoft scroll-snap-type property. The default value is "none".

A scrollable element is one for which 'overflow-x' or 'overflow-y' is 'scroll' or 'auto'.

scroll-snap-edges: none | margin-box | border-box

This property defines which (if any) CSS boxes for the element contribute to the allowable snapping positions for the nearest scrollable ancestor. Which edge of the box is used depends on the direction of the scroll operation. For example, when scrolling down, the bottom edge of the box is aligned with the bottom edge of the scrolling container. More details below.

Scroll snapping is applied at the end of each complete scroll gesture (e.g. after releasing the finger at the end of a touch panning gesture, after pressing an arrow key) to potentially adjust the final scroll destination. The vocabulary of scroll gestures, and their scrolling behaviors (e.g. animation physics) depends on UA considerations and is not (and should not be) defined here. However, we require that each scroll gesture have a logical start scroll offset and end scroll offset. (These need not correspond to actual rendered scroll offsets. For example if the user presses page-down to start an animated scroll operation, and then presses page-down again before that operation is complete, the second gesture's start offset might be the desired destination of the first gesture rather than the actual scroll position at that time.) Scroll snapping produces a snapped scroll offset to be used as the final scroll destination.

Scroll snapping for a scrollable element E is defined independently for horizontal and vertical offsets. Horizontally, if 'scroll-snap-x' is 'none' or the start and end offsets are equal, the snapped scroll offset is the end offset. Otherwise we collect the set of candidate boxes C as follows:

  • For each descendant D of E for which E is the nearest scrollable ancestor, where D's value of 'scroll-snap-edge' is 'margin-box', D's first margin-box if it has one.
  • For each descendant D of E for which E is the nearest scrollable ancestor, where D's value of 'scroll-snap-edge' is 'border-box', D's first border-box if it has one.

If the start offset is less than the end offset (scrolling to the right), take the right edge of each box in C. The snapped scroll offset is the offset that aligns the right edge of the scrolling container with the right edge of a box in C, and requires the least rightward scrolling. C edges that require leftward or no scrolling are not considered. If the start offset is greater than the end offset (scrolling to the left), take the left edge of each box in C. The snapped scroll offset is the offset that aligns the left edge of the scrolling container with the left edge of a box in C, and requires the least leftward scrolling. C edges that require rightward or no scrolling are not considered.

  • If 'scroll-snap-type-x' is 'proximity', the UA can choose to scroll to the snapped offset or (e.g. if that's too far away from the end offset), it can scroll to the end offset instead. If no C edges can be considered, the snapped offset is the end offset.
  • If 'scroll-snap-type-x' is 'mandatory', the UA can choose to scroll to the snapped offset or (e.g. if that's too far away from the end offset), it can reject the scroll gesture entirely. If no C edges can be considered, the snapped offset is the furthest offset in the direction of scrolling.

In any of these cases the UA can animate the scroll operation and adjust the actual scroll position to the destination over some period of time.

Vertical offsets are handled analogously.

The above algorithm implicitly requires transforming a box edge up to the coordinate space of the scrolling container. These transformations must take into account CSS positioning but not CSS transforms (so the orientation of boxes is guaranteed to be preserved).

UAs should apply scroll snapping to all user scroll gestures (including keyboard, scrollbars, etc). Script-driven scrolling (e.g. setting scrollLeft or scrollTop) is never affected by scroll snapping. Layout changes that affect the positions of elements with 'scroll-snap-edges', or dynamic changes to values of 'scroll-snap-edges', do not trigger snapping in the absence of a scroll gesture, even if 'mandatory' snapping is requested. However, UAs should ensure that with mandatory snapping, any sequence of scroll gestures followed by a steady state with no style or content changes results in the scroll position at the edge of some scroll-snap-edges element, or the end of the scroll container, depending on the direction of the last scroll gesture that was not rejected.

It would be nice to have 'scroll-snap-type:mandatory' ensure that at all times we're scrolled to the snap-edge of some element, even after arbitrary content and style changes. However, it's not clear how that should behave in many cases. For example, without a scroll gesture direction, it's not clear whether you should snap the top of an element to the top of the container or the bottom of an element to the bottom of the container.