The Web Needs Containment
Make me a view in HTML. Go on. You can't, of course, because the browser has no idea what a view is. And that means that we end up fighting with the browser, and that's never a good idea. Let's look at why this even matters...
## Documents: they’re not a kind of app.
MVC frameworks are the de facto way to structure a web app. A quick tally of TodoMVC shows about 50 or so different flavors of MVC framework that you can use for your next project. That’s great and everything, but let me ask you this: how do you semantically declare a view in your HTML? And I don’t mean for the sake of screen readers and accessibility (though that would be good!) but to declare your intent to the browser? How does Chrome, Firefox, Safari, Opera or IE know that it’s dealing with a view? Because if the browser doesn’t know it can’t optimize for it.
Today, of course, we have HTML and styling that’s grown up out of the primitive of the
document. That’s no bad thing, but now we’re trying to build apps and that is increasingly like trying to teach an old dog new tricks. Today our rendering model assumes that if I change an element somewhere in the tree, that change will cascade down to child elements and potentially affect all of them. Those changes will also propagate back up the tree as well (yay!) and the entire document may need to be laid out.
<!-- This is so legit --> <section class="view isolatedforlayoutandpaint ipromise"> This is a document section desperately trying to be a view. It isn't. It's a SECTION of a DOCUMENT. </section>
If we’re making an MVC app and we want to have – say – five different views, all of which are at the root. They probably have subviews like lists and whatnot, this being an app and everything. Naturally we want (and expect) the browser to contain those views for styling, layout and paint. This would be great for performance, but also our sanity. But since we can’t declare a view we can’t benefit from that!
No views… and insane z ordering!
You simply have to just go the whole hog and learn how everything plays out (or should play out, bugs notwithstanding) and then massage your CSS to get things working.
Relatedly, things like stacking contexts and z-indexes are deeply confusing and end up horrible to manage. Behaviors change depending on whether an element is set to
You simply have to just go the whole hog and learn how everything plays out (or should play out, bugs notwithstanding) and then massage your CSS to get things working. And then there’s some weird crap about opacity.
Here’s something you may or may not know: Flash had (and still does have) the notion of self-contained views:
DisplayObjects. These are the base class of virtually everything on screen, and they behave like views! If you create two
DisplayObjects (or one of its subclasses like
Bitmap) you can easily move them around and know they won’t affect each other.
You can also have parent-child relationships, so you if you go all meme on it (“Yo dawg, I heard you like
DisplayObjects, so I put a
DisplayObject in your
DisplayObject so you can’t object to displayin’
DisplayObjectz”) you can build trees of these self-contained items. Internally each has a consistent and predictable z ordering, i.e. they are all stacking contexts, and, since the stage is effectively a
DisplayObject you also have z ordering there, too.
To be clear, I’m not saying I want develop in Flash again; the web has better accessibility and multi-device support, but that doesn’t mean we should discard what it does well!
Broaden the horizon
But let’s not get bogged down in Flash, eh? It’s still a bit raw for some folks and I’m pretty certain their pitchforks are just lying around begging to be sharpened. Instead let’s take a look a little bit further afield and see whether other platforms outside of the web have the notion of containment / views and, for giggles, sane z ordering:
|Sane Z Ordering||Yes||Yes||Yes||Yes||No|
Yeahhhhhh… all these other platforms have the basic notion of a view, and from there we find clear z ordering. Android and Windows both stand out to me as having an optional flexible layout model, something that is de facto on the web of course. But, crucially, when you change something inside one of your “views” on the web, the chances are everything else is going to be affected.
In a previous blog post discussing benchmarks I had this chart showing how style calculation times escalate proportionally to the number of affected elements:
If we want to maintain smooth framerates we need to avoid this work, and doubly so if the developer never intended for all those elements to be affected by a change. That chart doesn’t account for layout or paint, both of which would also increase. Oh and on mobile handsets times would increase by ~6-8x.
We are applying hacks to cajole the browser into treating elements like self-contained views.
Of course today there are implementation-specific criteria you can apply to your elements that get them to act as Layout Boundaries, but these are not always simple to apply and they are tied to the implementation. It’s the same with
translateZ(0) and chums; we are applying hacks to cajole the browser into treating elements like self-contained views.
But… Web Components, Paul! They shall fix our woes!
You might think so, right? Component implies full containment? Not so.
I actually think this should be a default for Web Components because if you include a 3rd party component it would be reassuring to think it can’t hose your entire page whenever it requires layout or paint. But as of today, because there is no API or spec for such a behavior, that’s not the case.
iframes do all this, right?
Yes they do, and that’s how Sencha made Fastbook, but I think there are problems with that approach:
- We “lose” styling information, scripts and so forth so now we have to either pump them in or have the frame request them directly. Ewww!
- Communication is done with
postMessage, which is neither fun nor a view-like API. We aren’t able to dig into its guts from the outside very easily. You could argue that’s a good thing, but I’d say it’d be frustrating for many people, and it’s something I think should be decided by a developer.
iframes are getting, at least from a Chrome perspective, additional plumbing to take them out of process, something you would probably not expect to be applied to a view. This appears to be for when
iframecontent is cross-origin, but there may end up being other same-origin cases where it also applies, it’s not totally clear.
That said, if you do want to get view-like isolation today, chances are an
iframe is your best bet, even if it is a bit of a square-peg-in-a-round-hole scenario.
Lazy block layout, css-containment and will-animate
Thankfully there are some movements towards containment, or at least damage limitation when we have paint or layout invalidations, so allow me to illuminate:
- Lazy Block Layout: this takes the approach of restricting fine-grain layout to only what can be seen in the viewport, thus much reducing the time spent in layout. (It estimates the layout for elements outside of the current viewport.) Generally I think this is A Good Thing™, because not everything is a web app and we still have documents that this will benefit. For apps, however, this still doesn’t provide complete containment, it just reduces layout cost globally.
- CSS Containment: this property allows you to guarantee a boundary around an element for both layout and paint, assuming you opt for a value of
strict. For me this is a lot closer to the notion of a self-contained view. The spec is still in draft as far as I know, but I’m hopeful it will see the light of day.
- will-animate: this sucker, proposed by Benoit Girard at Mozilla, is hot off the press, and a declarative way for developers to say that an element is going to change visually. That may be position (left, top et al) or it may be the element’s contents. This is a huge leap forward when it comes to telling the browser that it should expect, and therefore optimize for, visual changes. That puts the boot in on the browser having to infer intended behavior, which is splendid. And terrific. And marvelous.
As an industry we talk so much about “web vs native”, and yet actually the web doesn’t have the same notion of an application as other platforms. If we’re going to build the next generation of web apps we need to think about the primitives we need. We can learn from old hands like Flash, or MVC powerhouses like Android, iOS and Windows.
The web needs containment (or views if you want to call them that) because it’s eminently browser-optimizable. Much more than that, however, it’s a sane way to build apps.
Why else would we have so many MVC frameworks?