I’m not sure we’ve gotten much better at this since Tim Kadlec wrote this in 2012:
Stop me if you’ve heard this one before.
“Responsive design is bad for performance.” “User agent detection is bad. Don’t segment the web.” “Hybrid apps don’t work as well as native apps.” “CSS preprocessors shouldn’t be used because they create bloated CSS.”
… Find out for yourself if the tool is really where the blame should be placed.
I’m sure there is some psychological concept that explains why we transfer blame from the offending thing to what we perceive to be the cause.
Sometimes we’re good at this. Remember the AMP letter:
The AMP format is not in itself, a problem, but two aspects of its implementation…
Or the fact that accessibility issues aren’t React’s fault. Pointing at the tools makes it harder to talk about the real problems that need to be resolved.
Sometimes I’m not so good at this. I’m linking to Tim here in an effort to help me remember this.
I know. You hate parallax. You know what we should hate more? When things that used to work on the web stop working without any clear warning or idea why.
Way back in 2014, Keith Clark blogged an exceptionally clever CSS trick where you essentially use a CSS transform to scale an element down affecting how it appears to scroll, then “depth correcting” it back to “normal” size. Looks like we got five years of fun out of that, but it’s stopped working in iOS 13.
Here’s a video of official simulators and the problem:
After we shared that in our newsletter, we got an interesting reply from Michael Gale:
What about folks who love their animated GIFs, but just didn’t want the UI to be zooming all over the place? Are they now forced to make a choice between content and UI?
I thought that was a pretty interesting question.
Also, whenever I see <img src="gif.gif"> these days, my brain is triggered into WELL WHAT ABOUT MP4?! territory, as I’ve been properly convinced that videos are better-in-all-ways on the web than GIFs. Turns out, some browsers support videos right within the <img> element and, believe it or not, you can write fallbacks for that, with — drumroll, please — for the <picture> element as well!
Let’s take a crack at combining all this stuff.
Adding an MP4 source
The easy one is adding an additional <source> with the video. That means we’ll need three source media files:
A fallback non-animated graphic when prefers-reduced-motion is reduce.
An animated GIF as the default.
An MP4 video to replace the GIF, if the fallback is supported.
Under default conditions in Chrome, only the GIF is downloaded and shown:
Under default conditions in Safari, only the MP4 is downloaded and shown:
If you’ve activated prefers-reduced-motion: reduce in either Chrome or Safari (on my Mac, I go to System Preferences → Accessibility → Display → Reduce Motion), both browsers only download the static PNG file.
I tested Firefox and it doesn’t seem to work, instead continuing to download the GIF version. Firefox seems to supportprefers-reduced-motion, but perhaps it’s just not supported on <source> elements yet? I’m not sure what’s up there.
Wouldn’t it be kinda cool to provide a single animated source and have a tool generate the others from it? I bet you could wire that up with something like Cloudinary.
Adding a toggle to show the animated version
Like Michael Gale mentioned, it seems a pity that you’re entirely locked out from seeing the animated version just because you’ve flipped on a reduced motion toggle.
I’m fairly sure there is no practical way to do this declaratively in HTML. We also can’t put this button within the <picture> tag. Even though <picture> isn’t a replaced element, the browser still gets confused and doesn’t like it. Instead, it doesn’t render it at all. No big deal, we can use a wrapper.
We can position the button on top of the image somewhere. This is just an arbitrary choice — you could put it wherever you want, or even have the entire image be tappable as long as you think you could explain that to users. Remember to only show the button when the same media query matches:
Shepherds are good at tending to their sheep, bringing order and structure to their herds. Even if there are hundreds of those wooly animals, a shepherd still herds them back to the farm at the end of the day.
That’s what we’re going to look at in this post. The Grid Shepherd technique can bring order and structure to the data we work with while giving us greater visibility to where and how it’s being used than we would be able to otherwise.
Let’s dig in.
We’re going to start by iterating over an unordered array of farm animals. Imagine that cows and sheep are happily grazing the fields. They can be grouped together programmatically with the Array.prototype.sort method and listed on a page:
To herd the animals, we have to fence them into a common area, which is what we’re using the <main> element to do. By setting that fence with display: grid, we’re creating a grid formatting context where we can define the column (or row) each animal should occupy.
And with grid-auto-flow: dense, each animal orders itself into the first available spot of each defined area. This can also be used with as many different sort options as you want — simply define another column and the data will be shepherded magically into it.
Quantity queries rely on some sort of selector for counting the classes — which would be great with the :nth-child(An+B [of S]?) pseudo-class notation, but it’s currently only available in Safari). That means we have to use the :nth-of-type() selector as a workaround.
We need some new element types for this to work. This could be realized through Web Components or renaming any HTML element to a custom name. This works even if these elements are not in the HTML spec, as browsers use HTMLUnknownElement for undefined tags which results in them behaving much like a div. The document looks now like this:
Here, we’re going to reach for Vue to dynamically add and remove animals using Vue transitions with two different sort options. Watch as the animals naturally occupy the correct columns, even as more are added and some are removed:
Grid Shepherd can also be used with any non-ordered data to:
separate and count voters in a poll (maybe as two sections with their corresponding profile picture) with live insertion;
group people / co-workers according to their position, age, height; and
create any hierarchical structure
Shepherding and accessibility
grid-auto-flow: dense does not change the DOM structure of the grid — it merely reorders the contained elements visually. A side effect can be seen in the last example when ordering alphabetically as the counter numbers get mixed up. Changing the DOM structure not only affects people who use screen readers, but also effects tab traversal.
Also note that a flat document structure might not be good for screen readers. Instead, I would treat these presentational grids as graphs and provide the information with a longer textual alternative.
Round ‘em up!