CSS Containment Specification
developer.mozilla.org
developer.mozilla.org
1) Re-rendering of virtual DOM content
2) Mutating the real DOM to match that new virtual DOM
3) The browser actually drawing the new state of the DOM/styles
#1 is your JS code. #2 is covered opaquely by React and peers. #3 is traditionally covered opaquely by the web browser.
This API is a way of giving hints to #3 when that becomes your bottleneck (which is really quite rare, but when it happens, it's extremely nice to have a recourse). If you're experiencing "jank" and are unsure whether this will help, profile your app in Chrome and look for the purple "Reflow" bars in the flame chart. If your chart is mostly orange, your JavaScript is still your bottleneck.
This API is analogous to the compiler hints (assertions, etc.) you can add in certain native languages which allow the compiler to make certain assumptions and therefore optimize more aggressively, where it otherwise might have had to play it safe to ensure correctness.
One common source of performance issues is that certain JS DOM operations implicitly force the browser to do new layout, so you can end up having to wait for it.
For vdom the term used should simply be re-computing or re-calculating or updating or diffing.
Indeed language evolve to a local minima of being good enough to understand each other good enougthly in most cases. I would have hoped language evolution to improve over time but the world is not ready for creating an institution that would allow that. In the meantime, I and others that understand that maximising semantical understanding matter, will keep voicing.
That said, I can see how the multiple adjacent uses of the term might get confusing
Virtual DOM isn't even a real thing. Its some fiction from React. It is really unfortunate to see the standard layer bloated to compensate for the shortcomings of some framework.
None of the benefits provided by this concept seem necessary, as in these same capabilities and performance solutions were already there by other means.
That's not remotely what's happening. You could have the same issues with (and use this API on) a jQuery-mutated DOM.
It effectively allows developers to opt in to a more restricted set of functionality in return for more optimisations. Kind of like gradual typing for UIs.
I'd like to see more emphasis on these kind of improvements from browser vendors, because most things can be worked around with custom implementations, but performance cannot!
Perhaps we could get limited capability lightweight DOM nodes too?
Even for a personal site, I wouldn’t hold back on sticking hundreds of images in a blog post. Honestly, UX of looking through those images is probably a bigger concern for me than bandwidth.
It would really help if browsers added support for Basis images though, because those can decode (quite efficiently!) to GPU compressed textures that cost less power to composite into the framebuffer.
I like the idea of containing floats in a div easily, no more clear hacks lol. But... I must be missing something since it really seems to be these optimizations ought to already be possible.
I think the goal is to avoid analyzing child elements at all. So you can layout a given set of children at one layer of the DOM tree at once rather than having to dig deeper into one node to know where to put its sibling.
> And if you add overflow: auto or hidden, wouldn't that infer to contain: strict?
I think browsers would only be able to reasonable infer "contain: strict" on elements with both overflow auto/hidden and fixed width/height.
I'm guessing that "contain: strict" would allow you to avoid defining fixed height and width then? (That was just my first guess. There may be other reasons.)
If you add contain: strict to the container in the above example you'll see that it then successfully clips the child div.
Not just children, but descendants at arbitrary depth. Also no relative positioning, negative margins making anything stick out, no transforms, no shadow with a radius large enough to poke out of its parents...
> And if you add overflow: auto or hidden, wouldn't that infer to contain: strict?
No, you also need to make sure that the size of the parent element is not influenced (horizontally or vertically) by the size of any descendant.
Well, some of these are allowable if the effects don't poke out of the particular element you're interested in.
Effectively, this is all stuff the browser could check before turning on an optimization that depends on these conditions being verified, but due to the number of things you need to check on a tree of arbitrary depth, the verification would cost more than the saving you'd get by applying the optimization. So browsers don't do it.
css-contain gives you a mode where there's nothing to check, because the basic rules of layout/painting/sizing are changed so that things can never leak up if you've turned on the relevant type of containment.
I’m in favor of browsers inferring whatever they can, but when it comes to critical performance sections I want to be explicit.
It goes further than that: using contain means that even if you tried applying these styles that would break optimizations, the browsers will not obey you. css-contain isn't just a hint to the browser that you're not screwing things up, it's a mode switch that prevents you from doing so.
Contain gives you something approximating a guarantee and you also know the exact effect of the attribute.
I wonder which will come first: Safari support for contain or Safari support for input type=date.
I've tried this with CodeMirror 5 and it doesn't break anything. Hopefully over time VS Code and other editors can back out their complex custom renderers.
If a file contains tens of thousands of lines (not uncommon these days) the cost of duplicating all of that to a complex HTML+CSS tree is not insignificant, even if contain reduces the cost of it.
Even now it's not hard to get the github code view of a large file to lag a bit in Firefox when scrolling it because of how much layout and rasterization has to happen (in part due to the way they aggressively syntax highlight the text and split lines into elements to include line numbers)
Another example - if you haven't done it recently, try opening a 10mb+ text file in chrome or firefox. The experience is still somewhat bad.
Also 2): if I understand correctly, if contain: content is applied to a parent divs, it will apply to all it's child divs. So it would be a bloat and a noop to apply contain:content to it's child divs? Therefore is the optimal selector: body > div { contain: content; } or is it div { contain: content; Or is it * {} (and if so, with or without pseudo elements?)
Edit: regarding 2) it seems to be refuted as I've read that all elements by default are not and inherited to false, therefore contain on a parent does not set contain on it's children. But 1) is still unknown and not answered anywhere which is a shame.
I could actually see this being pretty compelling for easy performance wins. Its much easier to adopt a single CSS property versus a different model for DOM interaction. The churn in the spec and various implementations didn't help shadow DOM adoption at all.
Its 2020 and I've been using shadow DOM(plus web components) for 6 years in side projects. When the stack has native support its great; it runs fast and is a pleasure to develop. The downside is you have to be willing to swallow polyfills that degrade performance when you don't have native support.
If 2020 isn't the year of shadow DOM, maybe 2022 can be the year of shadow DOM polyfills that don't ruin performance.
Shadow DOM isolates selectors, style sheets, etc. It’s about a public/private API for elements.
Contains is about layout and rendering hints to the browser.
The first thing I did was rip out the layout engine and give everything on the page extents at load time. Eventually, scrolling involved finding the range that fell within the viewport and rendering that. That seemed like what any video game designer would do for a side scroller. I just turned it 90°. Ended up about as fast as Openwave and much smaller.
This article sounds as if CSS3 browsers don’t or can’t do that. I find that quite surprising. Unless we’re using a different definition of render (I still had to lay everything out).
However, if change the layout / content of some element in the page, making it bigger, or smaller, or positioned differently, etc, that has an impact on its parents, recursively. So browsers mark subtrees as dirty when something changes, and work their way back up.
In certain cases, there are changes that cannot have an effect on the parents, so you should be able to stop the recursion. And browsers typically are reasonably smart about that.
However, there are a surprisingly large number of cases where checking whether or not the parents could possibly be affected by the change is an expensive operation, so you're better off relaying out your parents (recursively) rather than checking if you could safely skip doing it.
Css-contain changes the rules of how css works, to eliminate these expensive to check cases. contain:content makes a few useful things impossible. contain:strict is even harsher, as parents' size cannot depend on children's size. But if you happen to have an element that can be set up to fit within these constraints, turning containment on on the right element lets the browser know that a whole bunch of optimizations are safe without checking for preconditions.
`contain: size` means that the element's size does not depend on its children. Isn't this already given by height, width, etc.? Why is this needed? If these properties disagree, who wins?
`contain: paint` means descendants cannot display outside the element's bounds. Isn't this already given by `overflow: hidden`? Why is this needed? If these properties disagree, who wins?
`contain: layout` means that its internal layout is not affected by anything outside. It's not clear to me what behavior they have in mind. Is it something `position: relative` would fix?
The other way around:
* normally, a float can poke out of its parent div, and affect stuff around
* an absolutely positioned element can poke out of its parent, and if a further ancestor is overflow:auto and the absolutely positioned thing goes far enough, it could trigger scrollbars, whose appearance could cause a relayout
* margins of children can collapse with the margins of parents (recursively), and affect the layout of ancestors
* there are more like that
> `contain: paint` means descendants cannot display outside the element's bounds. Isn't this already given by `overflow: hidden`?
Almost: `overflow:hidden` actually makes the element programatically scrollable. It doesn't discard everything that sticks out, because you might just start scrolling using JS, so the browser needs to keep a buffer with the out -of-view stuff ready, just in case, even if it's unlikely. Or maybe it'll optimize a bit more, and create these buffers on demand, but it still needs to have facilities to keep track of which buffers it has, which it could create, what font would need to be downloaded to render the out-of-view part…
> If these properties disagree, who wins?
contain wins. The point of contain is that if it's on, the behavior is guarateed, and the browser doesn't need to check a dozen properties on an arbitrary number of elements before knowing if certain optimizations are safe to do. So if it's on, it's on, and there's no way to break out.
> `contain: size` means that the element's size does not depend on its children. Isn't this already given by height, width, etc?
* there's an etc here, and it turns out that there a few more properties than you'd expect that need to be checked. Doable, but checking 13 properties takes more time than checking 1, and we're trying to turn on optimizations. Expensive checks before you can optimize can make the optimization not worth pursuing.
* There are cases where even with `width` set to something other than a fixed size, the width of the parent doesn't depend on children. But checking if you're in one of these cases can be complicated, if it isn't being guaranteed by something like contain.
Regarding `overflow: paint`:
In <https://www.w3.org/TR/css-contain-1/#containment-paint> it says:
> ... This does not include the creation of any mechanism to access or indicate the presence of the clipped content; nor does it inhibit the creation of any such mechanism through other properties, such as overflow, resize, or text-overflow. This is as if overflow: visible was changed to overflow: clip at used value.
That seems to imply that `overflow: hidden` could still be used with `contain: size` to "access" clipped content. And still I don't see how the optimizations they list are not available with `overflow`.
BTW, I think it's unfortunate that the MDN article introduces `contain` as if it were a hint...
> This information is something that is usually known, and in fact quite obvious, to the web developer creating the page. However browsers cannot guess at your intent ...
... when actually it modifies layout and display behavior, overriding other properties.
Ah, you're right. This changed at some point in the history of this property, and I was remembering the old version.
> And still I don't see how the optimizations they list are not available with `overflow`.
Here's one: Setting overflow to something other than visible doesn't cause the element to be a containing block for absolutely positioned children, so they can escape. https://jsbin.com/wesirup/edit?html,css,output
Here's another one: stacking contexts are weird https://jsbin.com/hepiqof/edit?html,css,output css-contain:paint puts sensible boundaries.
> BTW, I think it's unfortunate that the MDN article introduces `contain` as if it were a hint... ...when actually it modifies layout and display behavior, overriding other properties.
Agreed.
The same caveats seem to be present for strict containment mode though. The width/height of a strictly contained element wont't update if its contents change.
1) Re-rendering virtual DOM content
2) Mutating the DOM to match that new virtual DOM
3) The browser actually drawing the new state of the DOM/styles
#1 is your JS code. #2 is covered opaquely by React and peers. #3 is traditionally covered opaquely by the web browser.
Those libraries typically focus on optimizing step #1. The OP is a way of giving hints to #3 when that becomes your bottleneck (which is really pretty rare, but when it happens, it's extremely nice to have a recourse).
I guess that's already been done anyways. Anyone care to help me understand why this lives here?