|
1 | 1 | # Container Query Polyfill |
| 2 | +A small (9 kB compressed) polyfill for CSS Container Queries using [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) and [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) supporting the full [`@container`](https://drafts.csswg.org/css-contain-3/) query syntax: |
2 | 3 |
|
3 | | -A tiny polyfill for [CSS Container Queries][mdn], weighing about 1.6kB brotli’d. It transpiles CSS code on the client-side and implements Container Query functionality using [ResizeObserver] and [MutationObserver]. |
| 4 | + * Discrete queries (`width: 300` and `min-width: 300px`) |
| 5 | + * Range queries (`200px < width < 400px` and `width < 400px`) |
| 6 | + * Container relative length units (`cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, and `cqmax`) in properties and keyframes |
4 | 7 |
|
5 | | -## Usage |
6 | | - |
7 | | -Ideally, the polyfill is only loaded if the browser doesn’t support Container Queries natively. In a modern setup with a bundler that uses ES modules, the following snippet should work: |
| 8 | +## Getting Started |
| 9 | +To use the polyfill, add this script tag to the head of your document: : |
8 | 10 |
|
9 | 11 | ```js |
10 | | -const supportsContainerQueries = "container" in document.documentElement.style; |
11 | | -if (!supportsContainerQueries) { |
12 | | - import("container-query-polyfill"); |
13 | | -} |
| 12 | +<script type="module"> |
| 13 | + if (!("container" in document.documentElement.style)) { |
| 14 | + import("https://unpkg.com/container-query-polyfill/cqfill.modern.js"); |
| 15 | + } |
| 16 | +</script> |
14 | 17 | ``` |
15 | 18 |
|
16 | | -If you are in a legacy setup (or just want to prototype quickly), there’s also an IIFE version that you can include using a `<script>` tag: |
| 19 | +You may also wish to use a service to conditionally deliver the polyfill based on `User-Agent`, or self-host it on your own origin. |
| 20 | + |
| 21 | +> **Note** |
| 22 | +> All browsers have support for container queries released or on their roadmap, so it's recommended that you avoid bundling the polyfill with your other code. |
17 | 23 |
|
18 | | -```html |
19 | | -<script src="https://unpkg.com/container-query-polyfill/cqfill.iife.min.js"></script> |
| 24 | +For the best user experience, it's recommended that you initially only use the polyfill for content below-the-fold and use `@supports` queries to temporarily replace it with a loading indicator until the polyfill is ready to display it: |
| 25 | + |
| 26 | +```css |
| 27 | +@supports not (container-type: inline-size) { |
| 28 | + .container, footer { |
| 29 | + display: none; |
| 30 | + } |
| 31 | + |
| 32 | + .loader { |
| 33 | + display: flex; |
| 34 | + } |
| 35 | +} |
20 | 36 | ``` |
21 | 37 |
|
22 | | -## Browser support |
| 38 | +You can view a more complete demo [here](https://codesandbox.io/s/smoosh-glitter-m2ub4w?file=/index.html). On sufficiently fast networks and devices, or devices that natively support Container Queries, this loading indicator will never be displayed. |
23 | 39 |
|
24 | | -The polyfill relies on [ResizeObserver], [MutationObserver] and [`:is()`][is selector]. Therefore, it should work in all modern browsers, specifically Chrome/Edge 88+, Firefox 78+ and Safari 14+. |
| 40 | +> **Note** |
| 41 | +> Keep in mind that this technique effectively trades off LCP for less jank during initial load, so you may see regressions in the former as a result, particularly on low end devices. |
25 | 42 |
|
26 | | -## Feature support & limitations |
| 43 | +## Limitations |
27 | 44 |
|
28 | | -My aim is to make the polyfill work correctly for the _majority_ of use-cases, but cut corners where possible to keep the polyfill simple(-ish), small and efficient. The limitations arising from these tradeoffs are listed below. |
| 45 | +* **CSS first**: The polyfill currently only supports `<style>` and `<link>` elements. Inline styles via the `style` attribute or CSSOM methods are not polyfilled. Likewise, JavaScript APIs like `CSSContainerRule` are not polyfilled, and APIs like `CSS.supports()` are not monkey-patched. |
| 46 | +* **Best effort**: Style changes that do not lead to observable DOM or layout mutations (e.g. `font-size` in a container without content) may not be detected, or may be detected a frame late on some browsers. |
| 47 | +* Currently, there is no support for Shadow DOM, or functions like `calc(...)` in container conditions. Your contribution would be welcome! |
29 | 48 |
|
30 | | -(These decisions _can_ be revisited if they pose a significant hurdle and there is a good way to implement them. Please open an issue!) |
| 49 | +## Supporting browsers without `:where()` |
31 | 50 |
|
32 | | -- Both the old CQ syntax as well as the new syntax are supported: |
| 51 | +The polyfill uses the CSS [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) pseudo-class to avoid changing the specificity of your rules. This pseudo-class is relatively new, however. To support older browsers, you will need to append the dummy `:not(container-query-polyfill)` pseudo-class to the originating element of every selector under a `@container` block: |
| 52 | + |
| 53 | +<table> |
| 54 | +<tr> |
| 55 | +<td> Before </td> <td> After </td> |
| 56 | +</tr> |
| 57 | +<tr> |
| 58 | +<td> |
33 | 59 |
|
34 | 60 | ```css |
35 | | -/* These are all equivalent */ |
36 | 61 | @container (min-width: 200px) { |
37 | | - /* ... */ |
38 | | -} |
39 | | -@container (width >= 200px) { |
40 | | - /* ... */ |
41 | | -} |
42 | | -@container size(width >= 200px) { |
43 | | - /* ... */ |
| 62 | + #foo { |
| 63 | + /* ... */ |
| 64 | + } |
| 65 | + |
| 66 | + .bar { |
| 67 | + /* ... */ |
| 68 | + } |
| 69 | + |
| 70 | + #foo, |
| 71 | + .bar { |
| 72 | + /* ... */ |
| 73 | + } |
| 74 | + |
| 75 | + ul > li { |
| 76 | + /* ... */ |
| 77 | + } |
| 78 | + |
| 79 | + ::before { |
| 80 | + /* ... */ |
| 81 | + } |
44 | 82 | } |
45 | 83 | ``` |
46 | 84 |
|
47 | | -- Boolean operations (`and`, `or` and `not`) are supported. |
48 | | -- The polyfill does _not_ support style queries (e.g. `@container style(--color: red)`), as there is no way to get notified of computed style changes. |
49 | | -- The polyfill does _not_ support pseudo elements (::before & ::after), as they don’t have a real DOM handle and can't be observed with `ResizeObserver`. |
50 | | -- Container Queries will not work when nested inside a Media Query. For now, the polyfill only supports top-level CQs. |
51 | | -- Container Query thresholds can only be specified using pixels. |
52 | | -- Due to the nature of CORS, the polyfill only attempts to handle same-origin and inline stylesheets. Cross-origin stylesheets are not processed, regardless of CORS headers. |
53 | | -- CQs inside ShadowDOM are not supported yet. |
54 | | -- Don’t do weird interspersed comments, okay? Like `@container /* here’s a comment! */ (min-width: 1px) { ... }`. Just don’t. |
55 | | - |
56 | | -## Building & Testing |
57 | | - |
58 | | -This project uses [esbuild] to bundle the project, which is automatically installed via npm. To build the polyfill, run: |
59 | | - |
60 | | -``` |
61 | | -npm run build |
62 | | -``` |
63 | | - |
64 | | -To run the tests, run |
65 | | - |
66 | | -``` |
67 | | -npm run serve |
| 85 | +</td> |
| 86 | +<td> |
| 87 | + |
| 88 | +```css |
| 89 | +@container (min-width: 200px) { |
| 90 | + #foo:not(.container-query-polyfill) { |
| 91 | + /* ... */ |
| 92 | + } |
| 93 | + |
| 94 | + .bar:not(.container-query-polyfill) { |
| 95 | + /* ... */ |
| 96 | + } |
| 97 | + |
| 98 | + #foo:not(.container-query-polyfill), |
| 99 | + .bar:not(.container-query-polyfill) { |
| 100 | + /* ... */ |
| 101 | + } |
| 102 | + |
| 103 | + ul > li:not(.container-query-polyfill) { |
| 104 | + /* ... */ |
| 105 | + } |
| 106 | + |
| 107 | + :not(.container-query-polyfill)::before { |
| 108 | + /* ... */ |
| 109 | + } |
| 110 | +} |
68 | 111 | ``` |
| 112 | +</td> |
| 113 | +</tr> |
| 114 | +</table> |
69 | 115 |
|
70 | | -and open your browser at `http://127.0.0.1:8081/tests`. |
71 | | - |
72 | | ---- |
| 116 | +This is to ensure the specificity of your rules never changes (e.g. while the polyfill is loading, or on browsers with native support for container queries). On browsers without `:where()` supports, rules without the dummy will be ignored. |
73 | 117 |
|
74 | | -License Apache-2.0 |
| 118 | +## ResizeObserver Loop Errors |
75 | 119 |
|
76 | | -[mdn]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries |
77 | | -[resizeobserver]: https://caniuse.com/resizeobserver |
78 | | -[mutationobserver]: https://caniuse.com/mutationobserver |
79 | | -[esbuild]: https://esbuild.github.io/ |
80 | | -[is selector]: https://caniuse.com/css-matches-pseudo |
| 120 | +When using the polyfill, you may observe reports of errors like `ResizeObserver loop completed with undelivered notifications` or `ResizeObserver loop limit exceeded`. These are expected, and may safely be ignored. |
0 commit comments