[css-snappoints] Alternate Scroll Snapping Model

I want to start off by saying that I think scroll snapping is a great
idea, and I fully support exposing this functionality through CSS.
However, as roc has mentioned on the mailing list:
   https://lists.w3.org/Archives/Public/www-style/2013Aug/0224.html
   https://wiki.mozilla.org/Gecko:CSSScrollSnapping
and as I've mentioned at multiple F2Fs:
   https://lists.w3.org/Archives/Public/www-style/2014Feb/0607.html
   https://lists.w3.org/Archives/Public/www-style/2015Jan/0009.html
   https://lists.w3.org/Archives/Public/www-style/2015May/0282.html
a major weakness of the current proposal 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).

I'd prefer to build off of roc's model (see wiki above), and to that
end I'd like to point out the two major use cases that have been
presented, and a third that's been mentioned:

Use Case A: Snapping to the start or middle of each box
   e.g. address book (start) or photo album (middle)

Use Case B: 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

Use Case C: 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

And also the key question of
   What happens when the viewport is significantly larger or smaller
   than one item?
Whose answer I would like to be
   The user still has a pleasant experience despite the author not caring.

The Proposal
============

Properties set on descendants of the scroll container:

     scroll-snap-edges: <box> || <length>{1,4}
     scroll-snap-align: none | group | <alignment>
     scroll-snap-scope: infinite | finite

Properties set on the scroll container:

     scroll-snap-type: none | proximity | mandatory
     scroll-group-align: <alignment>

Where
     <alignment> = [ start | end | edges | center ]{1,2}
     <box> = border-box | margin-box

Initial values are as follows:
     scroll-snap-area: border-box;   /* Use edges of border box */
     scroll-snap-align: none;        /* No snapping of this box */
     scroll-snap-scope: infinite;    /* Snap positions extend infinitely,
                                        creating a grid of snap lines */

     scroll-snap-type: none;         /* No snapping in this scroller */
     scroll-group-align: center;     /* Snapped groups are centered */

Overview of Behavior
====================

   scroll-snap-align -
    Specifies the element's snap position as an alignment of
    its box within the viewport. If the 'group' value is used,
    it is group-snapped.

   scroll-snap-area -
    Specifies the box that is used for snapping. Defaults to
    border-box; can also specify margin-box and/or use offsets.

   scroll-snap-scope -
    Infinite causes snap edges to extend infinitely across the
    scrollable area. Finite limits the snap edges to the actual
    edges specified, so that e.g. boxes wholly outside the
    viewport don't influence snapping behavior.
    (Infinite is the initial value because for single-axis
     scrolling or gridded layouts, it doesn't matter, and
     this is probably the more performant option for UAs.)


   scroll-group-align -
    Collects all snapping edges 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.)

   scroll-snap-type -
    Tells the scroller to pay attention to snapping. (There were
    some issues raised on this design, but just copying the MS
    proposal for now.)

Examples
========

Use Case A:

   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 B:

   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 C:

   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;
       scroll-snap-scope: finite;
     }


   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;
       scroll-snap-scope: finite;
     }

Handling Small Viewports
========================

   The snapped position of a box is given by its scroll-snap-align
   property. This is a simple mapping to the current model. 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-point 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-point (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.

   (I'm not 100% sure of these behaviors or how best to describe them.
   But we need to handle the the case of larger-than-viewport items,
   and an area-based model lets us detect that case and do something
   intelligent about it, allowing the user to scroll around and see
   the entire box, rather than being clicked into mandatory positions
   that only allow partial views.)

Finite Snapping
===============

   When finite snapping is enabled, the "gravitational field" of a snap
   alignment is two-dimensional: distance to the snap position is
   calculated for both dimensions at once.

   In other words, if the snapping radius of influence is r, in infinite
   snapping the box snaps along the y axis whenever it is within r of its
   snapped y position, regardless of its x position. But in finite snapping,
   the box snaps along the y axis whenever it is within r of its snapped
   position in both dimensions.

   For example, a small box is snapped to the center of the viewport.
   It only snaps whenever it is < r distance in any direction from its
   snap position in both dimensions. In other words, it snaps whenever
   sqrt(dx^2 + dy^2) <= r for dx, dy as distance to the snapped position
   in the x and y dimensions respectively.

   As another example, a small box is snapped to the edges of the
   viewport. It only snaps whenever matching edges are within r of
   the respective viewport edges, so e.g. whenever its top edge
   approaches the top of the viewport, or its left edge approaches the
   left of the viewport; but there is no snapping effect if those edges
   are > r outside the viewport.

   (This feature can be safely deferred to a future level, if necessary.)

Combined Models
===============

   All features in this proposal can be combined within the same
   scroller: both finite and infinite snapping can co-exist, and both
   self and group snapping can be combined.

Shorthands and Longhands
========================

   There would be longhands, e.g. scroll-snap-block and scroll-snap-inline.
   Exact set TBD, based on need for independent cascading.

   There would also be a scroll-snap shorthand
     scroll-snap: <scroll-snap-area> || <scroll-snap-align> || <scroll-snap-scope>

Variants on the proposal
========================

   1. Defining <box> = [ border-box | margin-box ] || column-box
      to have multicols also provide snap lines between columns?

   2. Shifting scroll-snap-type to the descendants?
      (Initial value = proximity. Regions between a mandatory point
      and proximity points would be mandatory.)

   3. Shift scroll-snap-scope to the container?

   4. Rename various stuff?

There are obviously a lot of details to be worked out, but I think
this is a better direction to go in.

~fantasai (and TJ)

Received on Wednesday, 22 July 2015 19:11:19 UTC