To really sell streamed HTML’s impact, I should have shown the existing site’s design, but with streaming’s speed.
The demo’s design deviations
Unfortunately, it was impossible to have the existing design and the speed I needed. This post is about some of the differences.
If I were a good scientist, I’d have before/after traces for each deviation. Unfortunately, I do not — I was hastily testing and retesting on real hardware and a packet-throttled connection. If something felt slower, I tossed it.
If I’m wrong about something, I’ll happily accept corrections with a good source.
Design goals
Look, I’m jealous of native devs too. They get 60–120fps fullscreen animations with builtin platform support and minimum overhead.
But this is a website that sells food: I will relentlessly sacrifice delight for easier access. I can never truly catch up to native apps’ interaction richness, platform fidelity, and SFX flaunting. But I can beat native apps at the Web’s strength of getting things done, easily, with little fuss. I wasn’t just trying to outrace our existing site — I aimed at our native app too.1
I figured I could add flourishes and rise to meet interesting challenges after finishing the usable, accessible bones.
My amateur login page | The professional login page |
---|---|
Which design looks better, 10 seconds in?
That said, I’m no designer. A real designer could probably make these tradeoffs with better results.
Fonts
Kroger.com’s Nunito .woff2
files are ≈14kB each, totaling 55.3kB.
If I found a smaller lookalike font, subset aggressively, and applied more arcane font optimizations, that could probably shrink to ~12kB total.2 But remember the 20kB budget? 60% of that for a decorative font is hard to justify.
System fonts also spend less time on the main thread: each webfont src
causes a reflow (they can batch together, but don’t count on it). Usually the reflow isn’t awful, but you know who’s not too good for free performance? 👉 This dork! 👈
So, no webfonts.
But that was an easy decision: avoiding custom fonts is negative work. How about something harder?
Product carousels
Displaying products is ecommerce’s most important job. Which is why it was a problem that our scrolling product lists didn’t fit on the phone we sell:
That was the obvious reason to change, but there’s also subtler drawbacks:
- Memory pressure and increased layout cost
-
Scroll panes use RAM and VRAM for hardware-accelerated scrolling — but we need those for fast
<body>
scrolling, too!Remember how iOS Safari needed opting-in for accelerated scrolling with
-webkit-overflow-scrolling: touch
? Now you know why. - Scroll trap on touchscreens
The lists take up so much vertical space that they become infuriating scroll traps on the demo phones, like mobile map embeds. (It doesn’t help that cheap touchscreens often get swipe direction confused at the beginning of a gesture.)
- Reflow and pop-in
As new cards load on scroll, they all resize their height to match the tallest. Even when the shift is minimal, the necessary layout calculations still hiccuped on the demo phones.
- Poor use of small viewport
The Poblano wasn’t the smallest screen I targeted. I wanted to preserve legibility down to the Apple Watch.
Instead, I displayed products like this:
You can probably spot several tricks to save space in the above screenshot: product sizes inline with the names, relying on text’s horizontal nature for more wrapping compactness, etc.
I wanted to use Later, the “stickiness” of scrolling lists was cited to me as an important quality. I wanted to recoup some of that by decorating the “See more” links at the end of each product list with thumbnails of what more was on that list, but ran out of time before the demo. C’est la vie.⌛ Other things I wanted to do, but ran out of time
shape-outside
to flow text into the empty spaces of non-rectangular products, so I could make the image larger. (I had a burning desire to maximize how many onscreen pixels could be image pixels without sacrificing too much information density.) I might post how to do this, someday.
box-shadow
more like bourgeois-shadow
I know, I know. Shadows are fashionable. If used correctly they make interfaces more understandable. The interplay of light and shadow is the sum total of visual art itself.
But even from a strict design perspective, shadows aren’t all-upside:
- The area needed to spread/blur shadows requires space around elements, which comes at a premium on the small screens I targeted.
- Current popular use is subtle, which means they can never be the workhorse of a design. (Unless you make shadows the only design element, and that’s terrible.)
- An affordance that doesn’t meet contrast requirements is questionably useful for the people who need it most. Or even most people: glaring sunlight, tired eyes, distracted attention, etc. need strong affordances, not minimal ones. (I’m not even sure how many people can notice trendy shadows, from a visual accessibility perspective.)
And from not a strict design perspective…
Historically, box-shadow
is a career performance criminal. Probably not as much nowadays, unless some combination of inset, blur radius, border-radius
, transparency, shadowed element size, screen resolution, browser, OS, or hardware falls off browsers’ fast paths.
Modern advice mostly warns about transitioning or animating around shadows — especially since scrolling under them also counts, as Facebook learned.
(Yes, Google’s Material Design went all-in on blurring and animating lots of shadows. Have you seen how much effort they spend because of that?)
There are ways to work around shadows’ performance risks, which might even be worth the effort.
But… man, these box-shadow
s just don’t seem that worth it. They’re subtle! Intentionally! If I have to plan how to protect users from them, there needs to be way more of a payoff. At least skeuomorphism was in-your-face.
If a design effect is only noticeable to young people with good vision, and it can unpredictably penalize people with cheap or overworked devices… then I don’t care how delightful it is. The risks conflicted with my goals and this was my stupid passion project, dammit. I didn’t trust them and I didn’t need them.
So, I dropped the shadows. …but not like that.
Homepage promotions
Carousel | Tiles |
---|---|
Think about what code the carousel needs that tiles don’t:
- Next/previous swiping vs. buttons, and switching between them
- Position indicator
- Pause/Play (required for accessibility)
-
(prefers-reduced-motion)
check and mitigation - Accessibly hiding offscreen slides
- Autoforwarding
- Animation timing
- Repositioning slides to the left when wrapping
- Tab ↹ handling
No matter how efficiently those features are implemented, they’re still code users must download. (If users actually liked carousels that could be worth it, but, uh…)
But the demo didn’t use tiles either
Encoding each promotion as one big image has problems:
- Scaled-down text becomes unreadable small viewports
- Doesn’t work with High-Contrast Mode or other
forced-colors
- Takes too long to display anything on the target connection, which meant it was as good as not having it
- Costs users more than I was comfortable with. Especially since I couldn’t aggressively compress the images without making their text look crusty.
So the demo went even further beyond, with an approach I’m calling “ribbons” just now. Here’s how those load over the target connection:
No modals, tooltips, toasts, etc.
Unlike the parts of this post where it felt like I was compromising, this was a user experience improvement. Do you like dealing with modals and pop-up banners and all their annoying friends? Me neither.
That’s an opinion, though. Some more objective reasons I eschewed widgets for boring alternatives:
- Their JavaScript and CSS eat away at the performance budget
- They make less sense on small/touch screens — in particular, modals take up nearly the entire page anyway
- They’re hard to make accessible (modals in particular have been called “the final boss of web accessibility”)
Module | Minified | .min.gz | Slow 3G download |
---|---|---|---|
@popperjs/core 2.11.4
|
20.5 kB | 7.2 kB | 144 ms |
@reach/dialog 0.16.2
|
27.3 kB | 9.4 kB | 188 ms |
notistack 2.0.3
|
18.8 kB | 6.4 kB | 128 ms |
Total | 66.7 kB | 23 kB | ~½ second! |
✏️ Note: this table doesn’t include parse/execute time, which scales with minified kB.
(Yes, there are more efficient ways to script these widgets. I just grabbed whatever looked well-maintained and popular, like the vast majority of devs.)
Finally, implementing complex interactivity would have taken a lot out of me. Check out what it took for Pedro Duarte to make an accessible dropdown:
But if these UI patterns are annoying, not very accessible, and costly, why do so many sites use them?
- I think they’re easier to design, since they don’t have to care about the underlying page.
- I suspect analytics falsely report increased “engagement” because they need more interaction than less intrusive designs. Even if said interaction is, say, users trying to get the damn carousel to hold still.
- Sometimes, they can be the best solution for a problem — but as soon as you have that component, it’s tempting to reuse it for other, less-suited problems.
More on alternatives to these known user-aggravators:
- Designing for actual performance § Simplify the interface
- The good news: It doesn’t have to be a modal!
- The problem with snackbars and toast messages (and what to use instead) and its companion on tooltips, by Adam Silver
Better design via speed
Unlike the other sections where design was in service to speed, this section is about how fast page loads can enable better design!
Our existing checkout flow had expanding/contracting accordions, intertwingled error constraints, and tricky .focus()
management because of the first two. I wanted to avoid all that, so I broke up checkout into a series of small, quick pages:
If you’ve heard of One Thing Per Page, that’s what I did. It didn’t take long to code, and was surprisingly easy.
But wait! There’s more!
- low-confidence users find them easier to use
- they work well on mobile devices
- they’re better at handling things like errors, branches, loops and saving progress
I didn’t implement them for the demo, but functionality like user’s Account info and settings would also be well-served by this pattern.
[W]hen we started user research with the general public, we saw a very positive response to the simple step by step approach, even on large screens. Though it added more clicks, people said it made the process feel simple and easy - there wasn’t too much to take in and process at any one time. So we stuck with the simpler screens for everyone.
And thanks to Paint Holding these page sequences looked and felt as seamless as SPA navigation. (Lots more on that in the next post.)
So what?
Like how woodworking involves sculpting along the grain, web design needs an understanding of what’s easy and natural, to save effort for things that really need it.
Our current practices of pushing designers away from HTML & CSS thwarts an intuitive understanding of what’s easy vs. what’s hard on the Web. More cooperation and faster feedback loops could help — because I was designing and developing myself, the loop was as tight as possible as I switched hats on the fly.
But, uh, it’s also true my designs won’t win any beauty contests. So don’t trust me, listen to some actual designers:
- A Beginner’s Guide to Performant Design Decisions & The Path to Performance by Katie Kovalcin
- Designing for Performance · Lara Hogan
- What Web Designers Can Do To Speed Up Mobile Websites · Suzanne Scacca
- Performance As Design · Brad Frost
In the next post, I reveal that while I may be a twentysomething paid to work in React, I’m secretly a filthy progressive enhancement liker.
-
Native has much improved since Gruber wrote that post. It’s time web devs also stepped up our game, and not by playing catch-up: “What I missed when I dismissed them a decade ago is that web apps don’t need to beat desktop apps on the same terms.” ↩
Top comments (7)
Thanks for another great article and including a plethora of useful links. I also like the ongoing cost-benefit analysis. It's a refreshing change from the usual "there's a dependency for that" style of problem solving which doesn't look at the downstream costs that can be incurred.
This also reminded me of Web vs. native: let’s concede defeat (which also happens to reference Mobile Apps Must Die).
"I feel we’ve gone too far in emulating native apps. Conceding defeat will force us to rethink the web’s purpose and unique strengths — and that’s long overdue."
SPAs were seen as a disrupting force on the web, at a time were disruption was deemed necessary to avoid stagnation. I think this was just about the time Gen 0 was coming to grips with Progressive Enhancement.
Gen 3 looks like it could be a course correction, countering that disruption but also learning from Gen 1 and Gen 2.
Somehow I suspect you didn't start with React because it doesn't guide you to this kind of knowledge.
You’re absolutely right. I started with SVG to make webcomics, which I understand is extraordinarily atypical.
This sounds intriguing! Thank you for another very interesting (and fun to read!) post
It’s like using
shape-outside: url(…)
, but without needing the image to load — the idea was to inlinestyle="shape-outside:polygon(…)"
along thewidth
andheight
attributes to avoid layout shift.The steps without involving code are:
polygon(x1% y1%, … x𝑁% y𝑁%)
"In the next post, I reveal that while I may be a twentysomething paid to work in React, I’m secretly a filthy progressive enhancement liker." Looking forward to reading that one as well :)
Super well-written article and a nice guy, to boot! 👏
Wow. Just impressive.
Struggling with web app performance at the moment, and got some really useful tips here.
Thank you so much :)