Traditionally this problem was solved with JavaScript (usually in a framework such as JQuery), but the same functionality is now built into CSS, via Scrolling Snap Points (current draft).
The Concept
Let’s say we have a series of images arranged vertically in a container. In this case, the container is a <figure>
element which is further wrapped inside a <div>
:
<div id="scrollcontainer">
<figure>
<img src="floating-2x.jpg" alt>
<img src="jellyfish-2x.jpg" alt>
<img src="silk-2x.jpg" alt>
<img src="the-deep-2x.jpg" alt>
</figure>
</div>
The outer <div>
has been sized to fit around just one image exactly using the standard technique of employing relative and absolute positioning to preserve an intrinsic aspect ratio:
#scrollcontainer {
font-size: 0;
position: relative;
padding-top: 66%;
}
#scrollcontainer figure {
margin: 0;
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
While the figure
element is set to the height of its container, the other images will continue to show beyond the bounding area of the box, since web pages won’t hide ] content by default, even if it overflows a container. To change that behaviour, we must set the overflow
property of the figure
:
#scrollcontainer figure {
overflow: auto;
}
Now the container scrolls up and down, but can stop at any point, including halfway between two pictures; we want a scroll or swipe behaviour to “snap” the image column to the nearest photo.
CSS Scrolling Snap Points
To start, we must set the scroll-snap-type
on the container:
#scrollcontainer figure
scroll-snap-type: mandatory;
}
The mandatory
value means that the container must come to rest at a scroll snap point after a scroll or swipe behaviour. There is also a proximity
value, which means that snapping will only occur if the current position is sufficiently close to a snap point (proximity
is not yet supported in most browsers, as of this writing).
Next, we must set the scroll-snap-points
value. In this case, the images are in a column, so we’ll use the scroll-snap-points-y
property, with repeating value of 100%
, representing repeated units of the container’s height.
#scrollcontainer figure
scroll-snap-points-y: 100%;
}
Coordinates & Destinations
There are two other properties we must deal with: scroll-snap-coordinate
and scroll-snap-destination
. The former is applied to the elements we want to “stick” during the scroll, while the latter is applied to the nearest ancestor does not scroll itself. In our case, the code looks something like this:
#scrollcontainer figure {
scroll-snap-destination: 50% 50%;
}
#scrollcontainer img {
scroll-snap-coordinate: 50% 50%;
}
Essentially, these two values “map” each image to a point inside the scrolling container: the center of each photograph should stick to the exact center of its container. There are other possibilities, of course: making the scroll horizontal, rather than vertical (which would just require changing scroll-snap-points-y
to scroll-snap-points-x
; diagonal snap-scrolling, and moving elements of different sizes. I’ll cover these applications and more in future articles.
Unfortunately, Safari currently has a different interpretation of this value, and so is listed seperately with a a href="/217/CSS-Vendor-Prefixes-and-Flags">vendor prefix:
#scrollcontainer figure {
-webkit-scroll-snap-destination: 100% 100%;
}
#scrollcontainer img {
-webkit-scroll-snap-coordinate: 50% 50%;
}
Support, Prefixes & Polyfills
Support for CSS Snap Points is fairly good across modern browsers: Firefox has full support, and Safari (desktop and iOS) implementation has a few limitations. Perhaps suprisingly, Microsoft has supported CSS Scroll Snap Points since IE10, under a -ms
prefix. IE11 and Edge support the API on all screens (IE10 only supported it on touch screens.)
For browsers without support, I’d recommend Clemens Krack’s scrollsnap-polyfill, as has no dependencies (other than polyfill.js), uses requestAnimationFrame
for movement, and has a nice little added “bounce” on the snaps.
Photographs by Elena Kalis, Yarik.OK and Sergiu Bacioiu, licensed under Creative Commons.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/XXGqZB