Open ausi opened 7 years ago
Relevant thread with the Shopify team who implemented, then abandoned, JS-based container queries on their admin backend:
https://twitter.com/_lemonmade/status/870461985334362112
Here's what I think they implemented:
https://github.com/lemonmade/container-queries
I can't tell by looking at the code myself; any reason that’s FOUC-ier or less FOUC-y than cq-prollyfill?
Regarding requestAnimationFrame — does it matter that it happens just before layout (rather than between layout and paint, ala ResizeObserver)?
From reading the code I don’t see a reason for it to be FOUC-ier than cq-prolyfill. But it depends on how you load the JavaScript and when you call ContainerQuery.create()
.
The big problem I see with it is synchronous layouts and layout thrashing. I looks like for every node that has a container query attached a synchronous layout is performed. This can make it really slow. cq-prolyfill is optimized to do as few synchronous layouts as possible. I wrote about how this is done in https://github.com/ResponsiveImagesCG/container-queries/issues/3#issuecomment-185979829
requestAnimationFrame
running before layout is not a problem I think, the important part is that is should run before the very first render/paint so that we can avoid a FOUC. Once the DOM of the page is fully loaded (domready) we can remove the requestAnimationFrame
callbacks and switch to ResizeObserver
.
I thought you were looking to use requestAnimationFrame because ResizeObserver isn't really supported anywhere yet. If you're only hoping to use it so that your callback fires before first paint – why can't you use ResizeObserver? I think RO callbacks also fire before first paint.
Example: https://ericportis.com/etc/ResizeObserver-runs-before-first-paint.html Result: https://ericportis.com/etc/ResizeObserver-runs-before-first-paint-screenshot.png (this is a slightly-fleshed-out test of https://twitter.com/etportis/status/870673993207955457)
(PS the dirty nodes / sync layout stuff is still fuzzy in my head, need to dive into your code to really understand...)
I didn’t know that MutationObserver
works while the HTML document is still downloading, I thought it only observes dynamically injected nodes. Because of that, cq-prolyfill is already FOUC-free. You can test that with this example:
<?php function delay() { echo str_repeat(' ', 10 * 1024); ob_flush(); flush(); sleep(1); } ?>
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script> window.cqConfig = { postcss: true }; </script>
<style> div.\:container\(width\>100px\) { color: red } </style>
<script src="https://cdn.rawgit.com/ausi/cq-prolyfill/v0.3.3/cq-prolyfill.js"></script>
</head>
<body>
<div id="div1">div1</div><?php delay(); ?>
<div id="div2">div2</div><?php delay(); ?>
<div id="div3">div3</div><?php delay(); ?>
</body>
All div
s should be in red color as soon as they show up.
This is how that process for one of those div
s looks like in the browser:
div
So, when executed in the <head>
, cq-prollyfill getComputedStyle
s (and therefore computes layout) once (or, if a cq matches, possibly more than once) for every :container()
’d node?
Questions that come to mind (which I should be able to get some time to test/try to answer myself on Monday)...
Ran one little test, and yeah! In the head = FoUC free!
(http://ericportis.com/etc/fouc-finder/ vs http://ericportis.com/etc/fouc-finder/defer.html)
But—why does the FoUC-free version take 1.5x the time to paint a fully-queried-and-styled page (3.73s vs 2.46s), and is there any way to make it faster?
(http://ericportis.com/etc/fouc-finder/head.png vs http://ericportis.com/etc/fouc-finder/defer.png)
In my tests (with 20x CPU slowdown) the difference was not that big, the sync version took about 1.8 seconds and the defer version 1.6 seconds. But still, the defer version seems to be faster.
I think the reason for that is, that in the sync version the browser has to wait for the javascript to download and cannot parse the HTML and build the first layout in parallel.
The best compromise might be to load it async
instead of defer
. This would make it FOUC-free if the script is already in the cache.
Another performance problem I saw was that cq-prolyfill gets executed 4 times: One directly when the script loads, one called by the mutation observer, one for DOMContentLoaded
and one for the load
event. If all these events happen at the same time it might be possible to skip some of the executions.
I created issue #41 for the problem with multiple executions.
And regarding your questions:
Does it therefore exhibit the scary performance characteristics that browser folks have foretold, e.g. https://twitter.com/tabatkins/status/866828359308726275 ?
Sync resize is not a problem because browsers will only support ResizeObserver. Re-layout is expensive but cq-prolyfill is optimized to do as few re-layouts as possible, most of the time only one re-layout is performed, independent of the number of container query nodes. In the worst (very uncommon) case the number of re-layouts is equal to the nesting level of container query nodes, in your fouc-finder example this number would be five.
Does this mean that DOM parsing is halted/blocked until CSS is loaded and parsed?
Yes, but if we implement #41 this shouldn’t be a problem because for rendering the first frame the browser waits for the CSS anyways.
I think if the script loads fast enough and the page ist not too long it should be possible to avoid a flash of unstyled content.
We would need an event that fires directly before the first render, maybe a
requestAnimationFrame
? In the initial rendering case we should also try to run the process synchronously so that we don’t miss the first render.See also https://twitter.com/etportis/status/867023541614460928
/cc @eeeps