Setting native-like scrolling offsets in CSS with Scrolling Snap Points
One of the arguments in favour of native vs. Web is that of user experience; particularly when it comes to scrolling. A typical interaction on touch screens (especially tablets) is the sideways swipe to flip between items, such as the next/previous page or photo. Native apps handle this…well…natively. The platform deals with it, so the performance is good, and the behaviour is consistent between apps on the platform. Web apps, on the other hand, have to roll their own. This usually involves a bunch of complex JavaScript, and it isn’t hardware accelerated. As it is an emulation, the behaviour may differ from that on the user’s platform.
However, some platforms use web technologies for native apps. As such, the platform vendor has probably had to solve this problem using those tools. One such platform is Windows 8. Internet Explorer 10+ provide a bunch of vendor specific CSS properties for touch-based scrolling. As these have proven useful, the properties for scroll offsets have been recently been added to the standards track as Scroll Snap Points. In this post, I will quickly guide you through the functionality that is currently in the spec. As it is very new, everything is subject to change, but it does work as is in IE 10 and 11.
Setting snap point behaviour
The first step is to specify how snap points are used, if at all. This is set with the scroll-snap-type property. As with all properties in this spec, IE currently needs the -ms- prefix.
The default value is none which means that any snap points are ignored. If you want to enable them, you need to use the mandatory or proximity value. The first states that any snap points must be used, while the latter leaves it up to the user agent to decide if it is appropriate to use or not.
Specifying where the snap points lie
There are two properties to specify snap points; one for each scroll axis. The scroll-snap-points-x property handles the x axis, and (as you can imagine) scroll-snap-points-y handles the y axis. Both properties accept the same values.
Using regular offsets
If you want to scroll the same amount each time (such as scrolling a full page of content at a time, or if each item of content is the same width or height) you can use the snapInterval() function. The first value sets the position of the first snap point, and the second sets the interval for each subsequent snap point. Each value is a CSS length value or a percentage (of the padding box of the scroller).
In the following contrived demo, I have a bunch of divs, 500px tall. As there is a 100px top padding, I’ve set the first position to 100px, and the interval to 525px (the hight of the div, plus the margin). If you scroll down, each swipe will move exactly one element up or down. The very first swipe will scroll down 100px to the first snap point. If you scroll back to the start, you will not be able to scroll the first 100px into view.
#container {
-ms-scroll-snap-type: mandatory;
scroll-snap-type: mandatory;
-ms-scroll-snap-points-y: snapInterval(100px, 525px);
scroll-snap-points-y: snapInterval(100px, 525px);
}
div {
width: 500px;
height: 500px;
margin: 0 auto 25px;
}
Try scrolling up and down in the simple demo in IE10+
If you want to scroll the entire width or height of the scroller, you can use a percentage instead of a length. In this full viewport demo, I set the start position to 0% and the intervals to 100% (this is actually the default, but I’ve included it to illustrate the concept). I also set the scrolling container to 100vh and 100vw:
#container {
overflow: auto;
height: 100vh;
width: 100vw;
white-space: nowrap;
-ms-scroll-snap-type: mandatory;
scroll-snap-type: mandatory;
-ms-scroll-snap-points-x: snapInterval(0%, 100%);
scroll-snap-points-x: snapInterval(0%, 100%);
}Note: As Blink and Gecko no longer use prefixes for new properties, I’ve not included the -moz- and -webkit- prefixes. As the functions use camelCase rather than the usual hyphen separated CSS syntax, I suspect the names may change before the final version of the spec anyway.
Using different offsets
While snapInterval() is great if your content is a consistent size, there are times when the items you want to scroll into view may be irregularly sized. This is where snapList() comes into play. This accepts one to many comma separated values, each specifying a snap point. Each point is the actual position along the axis, rather than an offset from the previous point (this tripped me up at first).
In the following demo I have images that have different aspect ratios, so their widths are different. I want to scroll so that the next image moves to the start of the container when swiping left or right. I calculated the position along the x axis where each image starts and added that to the snap list:
#gallery {
-ms-scroll-snap-type: mandatory;
scroll-snap-type: mandatory;
-ms-scroll-snap-points-x: snapList(786px, 1643px, 2483px, 3264px, 4054px, 4402px);
scroll-snap-points-x: snapList(786px, 1643px, 2483px, 3264px, 4054px, 4402px);
} 
Try swiping the images in IE10+
There are a couple of things to bear in mind. Firstly, remember to set an initial snap point, so that when scrolling back, the first item can be seen (set to 10px in the demo). Secondly, if there is not enough content to scroll into view the snap point will not be used and you won’t be able to scroll to the last bit of content, so it is important to calculate the last snap point correctly. In this demo, you will see that the last scroll point is much shorter, so that it just reveals the end of the photo. If I tried to scroll the last image to the start of the container, there would not have been enough content to the right of the image, and the snap point would have been ignored.
Advantages of snap points
One of the key advantages is the simplicity of authorship. You can control the behaviour with just two CSS properties, rather than a bunch of custom JavaScript, or importing a library (that the end user’s browser has to download). The performance is also smooth, as the user agent can optimise the performance, and hand it off to the GPU.
Another huge benefit is that it behaves as expected on the platform. In IE’s case, if you slowly drag the image in the gallery, it will stick with your finger. If you let go just before half way between snap points, it will bounce back to the original position, while if you drag it a bit further, it will automatically scroll all the way. It handles all these small details for you.
Great, where can I use it?
Being a brand new spec compatibility is limited. It is fully supported in IE10 for touch, while in IE11 it is also supported for trackpad gestures, mouse, and keyboard, including scrolling by clicking on the scroll bar. Hopefully we will see more uptake as the spec matures.
You can find up to date support information in my CSS Scroll Snap Point support table.