-
Notifications
You must be signed in to change notification settings - Fork 10
Proposal for a slightly different syntax and function #3
Comments
To better describe the idea for the browser implementation here is an example: Lets go with a simple example DOM: <html>
<body>
<div class="container">
<div class="child"></div>
</div>
</body>
</html> And the following CSS: body {
padding: 10px;
}
.container {
float: left;
}
.child:container(min-width: 150px) {
background: green;
} And lets assume a viewport of 800x600. In the compute style step the browser would do the following:
|
Ok, that makes sense. You do need to lay out twice. You would need to split the layout algorithms in two: a first pass only doing the simplest size computation top-down for independent properties only, followed by a second pass doing dependent properties after you do your second style recalc. I am afraid the model is a bit complicated. It won't be easy to understand which parts of a document is viewport dependent, and which parts are independent. I quite like it to be honest. It would be hard to implement but it is a step in the right direction. |
Thank you for the great feedback! So you would suggest first doing a style calculation without container queries, then a descendants-independent layout algorithm, then a style calculation including container queries and then the second pass of the layout algorithm as the last step? Wouldn’t it be better for the performance to do the first pass of layout directly in the style calculation step? |
Layout is incredibly complicated. Having a internally consistent style during the entire layout is a useful simplifying assumption. I am not even sure you could do layout with no style for all descendents but I can't think of an example against that at the moment. Style collection and style resolution are pretty self contained. They are not particularly efficient but layout is an order of magnitude worse. I would not be too concerned about an extra style collection && resolution step if-and-only-if the total layout time remains unchanged (or is improved). |
The problem I see with splitting the style calculation and the first pass layout is that they have to run multiple times if elements with container queries are nested. In the worst case one time per nesting level. Layout is incredibly complicated, but AFAIK the parts that would be needed in the „first pass“ for the container queries should be much simpler. E.g. the direction for the computation is strict top down, it’s never necessary to know something about the child nodes to calculate the „container-width“. As you can see in step five of the example above, in cases where the full layout algorithm depends on descendants, the „first pass“ layout just computes |
Pardon my ignorance, were we to explicitly define container elements would that help avoid or speed up the second evaluation pass? Could a property trigger those children to be added to a collection that could then be evaluated? body {
padding: 10px;
}
.container {
float: left;
layout: container;
}
.child:container(min-width: 150px) {
background: green;
} |
In my example above the body element is used as the container for the query. As I read it again now, I should’ve named it The problem with explicitly defined containers is that the size of them may depend on its descendants. That the „right“ container is chosen by the browser is important IMO to solve the recursion issue and preventing CSS authors from mistakes. The CSS code you posted would result in a recursion if you add some dimensions: body {
padding: 10px;
}
.container {
float: left;
layout: container;
}
.child {
width: 200px;
}
.child:container(min-width: 150px) {
background: green;
width: 100px;
} For a complete demo of the recursion problem with the current syntax, you can take a look at ResponsiveImagesCG/cq-demos#2. |
Okay I think I'm understanding it all more. This makes a lot of sense.
Q: What about inheritance? Would the CQ evaluate on the inherit div.parent {
}
.child:container(min-width: 150px) {
width: 100px;
} |
Yes it would. In my example above A You can take a look at the functions isFixedSize and isIntrinsicSize of my prolyfill to see how it works there. It isn’t complete but handles the usual cases already. |
If someone is interested: I posted an update on my proposal and added a demo page for the prolyfill. The prolyfill script is now tested and pretty stable, so it should be ready to play around with it: https://github.com/ausi/cq-prolyfill. |
Have you thought about calling it
|
IMO What about A word which would make the selector readable as a sentence would be great too IMO, like it is with |
In my opinion In CSS
Isn't an element's query condition always retrieved from its parent node? The parent might have inherited the queries value but it still has the information, doesn't it? |
This is an implementation detail IMO and could be different in various browsers. As I’m no native speaker I don’t really know what fits better, I would be OK with any of But I think its a bit early for discussions about the correct name for container queries. We should check first if the proposed functionality is usable for CSS authors and implementable for browsers. As @BenjaminPoulain already mentioned “I quite like it to be honest. It would be hard to implement but it is a step in the right direction.” I’m optimistic for the browser side and my current intention is to get CSS authors to play around with the prolyfill. |
Is there any news regarding making a formal proposal towards W3C? Is there any news regarding browsers developers integrating this technology in their products? can see a lot of benefits using this functionality. Thanks! |
@stefanklokgieters IMO before making a proposal towards W3C, we need to know from browser makers if this version of container queries is any better than the others and if it is possible to implement it in a performant way. |
Agree: Let's not get jammed up on syntax right now. Let's stay focused on the ideal functionality. After many months of mulling this over, I think this is a pretty fool proof way to avoid the infinite recursion problem. I think we'd need feedback on:
I'd be happy to start soft-balling this to browser people for feedback on this technique. In the meantime maybe we should get some prolyfill performance stats on 1, 10, 100, 1000 container -queries being applied? Even if it's just JavaScript we can start getting an idea of time/memory footprint. |
Feedback from someone on the Chrome team:
Overall the need for isolated module styling was understood. This 2-pass layout thing seems like an issue but will keep asking around for feedback. I'm also putting together a little explainer doc so it's easier for people to catch up on the multi-year conversation. |
@davatron5000 Thanks for your help!
I will look into that and report my findings here. |
A React implementation perf example using rAF:
Looks like the 1000 one suffers a lot when resizing the window. But scroll performance is OK. But all of the demos are simple, might not be what you wanted. Implementation details see https://github.com/d6u/react-container-query/blob/master/src/createContainerQueryMixin.js#L27-L54 |
I tested the prolyfill with a simple container query: div:container(width > 500px) {
background: green;
} The result:
I also tested different nesting levels but that doesn’t change the result that much. |
@ausi A dumb question, how did you measure the resize time and initial time? Any doc/code I can learn from? |
@d6u I wrote a quick script to measure the speed, you can take a look at it here: https://gist.github.com/ausi/0f30d7568d2f93c04fa3 |
Great work, @ausi. This looks fantastic. |
I think limiting queries to elements that have ' If you don't have that limitation, then you have the problem that the size of the element (which you're querying on) can be influenced by the contents of the element, which can in turn be influenced by whether the query matches. #3 (comment) suggests this is doable with two passes, but I'm not quite sure I see how that works for handling of dynamic changes. The fundamental problem with handling dynamic changes is that we want a small change to content (e.g., adding a character of text) to have a small cost to re-layout. If you have an algorithm that's fundamentally two-pass, then you either (1) need to re-layout everything back to the first pass state and then re-layout everything again back to the second pass state, or (2) maintain separate data structures of the first pass and second pass states. For nesting of elements that use container queries, (1) would yield an exponential cost in time and (2) would yield an exponential cost in memory usage; I don't think either is likely to be acceptable as a performance/memory characteristic of the Web platform. (That said, I'm not sure that flexbox and/or grid haven't made this mistake -- which may be related to performance problems people are having with flexbox.) (How does your JS implementation handle these cases?) Even with the limitation to elements with ' |
@dbaron thank you for taking a look at it! If I'm understanding
My idea is to let the browser select which element the query gets matched against (the nearest qualified ancestor), so that a recursion cannot happen. But this selection only requires one dimension to be not influenced by the contents, the other (non-queried) dimension may still depend on the contents. Maybe adding a dimension to the
For small changes in the document, step 2 wouldn't find any changed container queries and no additional layout is needed. For most changes that trigger a container query, one second layout is needed. In the worst case the number of additional layouts is as high as the nesting level of container query elements. |
So saying the browser can select which element the query gets matched against isn't really a useful answer. Which element will it actually select, and how will it handle that? I can see that you'd want auto-sizing in one dimension, though. However, I don't think doing layout containment in only a single dimension is sufficient. It's not clear to me there's a sensible way to benefit from that across all of CSS's layout algorithms (e.g., flex, grid), some of which are rather complex. Though maybe they've managed to preserve some clever invariants, but I doubt it. So one problem with the algorithm that you describe in #3 (comment) is that it's dependent on what the previous layout was. We generally try to avoid making layout algorithms work such that you can get a different layout for the same DOM depending on the sequence of mutations that led to that DOM (though this may not be quite true for non-overlay scrollbars). But perhaps that's an ok invariant to break. It is scary, though, since it will lead to bugs where a site has a different display depending on the ordering of its incremental loading process (since incremental loading is effectively a sequence of dynamic changes) -- so effectively race conditions in layout. This also assumes that you've set things up so that the size (in the one dimension) of the qualified ancestor can't be influenced by the selectors. This is nontrivial; it probably requires most aspects of style containment, and it's far from clear to me that it's generally true across table layout, flex layout, and grid layout if you introduce something like single-axis layout containment. For some container query algorithms, it might be ok if this sometimes failed in edge cases. But combined with an algorithm like the one you described that's dependent on the previous state, it's pretty bad, since you could get into cases where each successive re-layout produces a one of two alternating states (or, with multiple queries, possibly even more complicated state machines), which is probably an even worse race condition. (The comparison here is against an algorithm that is expected to compute everything from an initial state right up front, but this has the problems I described in #3 (comment) .) |
For a width-query it will select the nearest ancestor whichs width doesn't depend on its contents. My JS impelementation currently traverses the DOM tree up until it finds an element with a fixed width, from this element it then traverses the DOM tree back down as long as the elements widths depend on their parent. It does that by checking the style of the elements against some simple rules, the rules for grid and flexbox are not (yet) implemented. One issue with this algorithm are scrollbars, which could change the inner width of an element depending on contents.
That was the most performant way I found for the JS implementation because I cannot hook into the layout process of the browser. An implementation in the browser may work differently. |
As far as baby steps go, would something like |
@tigt AFAIK it would be much easier and it is already implemented in iOS I think. Auto-resize iframes are currently being discussed in the www-style mailing list: https://lists.w3.org/Archives/Public/www-style/2016Mar/0198.html. |
@ausi good stuff! I like the idea about |
@henriquea Thanks! |
In #2 (comment) I posted an idea about a different syntax. It looks like:
And the nearest qualified ancestor is selected as the container to run the query against by the browser.
As I put more thoughts into this I got an idea of how the implementation issue (jumping between compute style and layout) could possibly be solved.
If I’m right the browser computes the styles by traversing the DOM tree from top to bottom. In this process it could already calculate and store the width if it knows that it doesn’t depend on its descendants. If it then reaches an element with a container query rule it already knows what the right container is – it’s the nearest ancestor for which it was able to calculate a width – and which width it has. So it should be possible to resolve the container queries without doing the layout process.
It may be that I’m totally wrong with my assumptions about browser internals, but it would be great if it is implementable this way.
If someone is interested in this idea I also wrote a prolyfill and a blog post about this version of container queries.
The text was updated successfully, but these errors were encountered: