Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
11 changes: 11 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
}
45 changes: 45 additions & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Build & Test

on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]

jobs:
build:
runs-on: ubuntu-latest
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
WPT_MANIFEST: ${{ github.workspace }}/wpt/MANIFEST.json
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/setup-python@v3
with:
python-version: '3.x'
- uses: actions/checkout@v3
with:
repository: devknoll/wpt
path: wpt
ref: x-polyfill-all-tests

- name: Build
run: |
npm install
npm run build:wpt

- name: Setup WPT
run: |
cd wpt
pip install virtualenv
./wpt make-hosts-file | sudo tee -a /etc/hosts
- name: Run Tests
run: |
npm run serve &
./wpt/wpt manifest
./wpt/wpt serve --inject-script=${{ github.workspace }}/dist/src/cqfill.modern.js &
npm test
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
cqfill.js
cqfill.min.js
cqfill.iife.min.js
dist
node_modules
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"bracketSpacing": false,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}
143 changes: 94 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,80 +1,125 @@
# Container Query Polyfill

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].
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:

## Usage
- Discrete queries (`width: 300` and `min-width: 300px`)
- Range queries (`200px < width < 400px` and `width < 400px`)
- Container relative length units (`cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, and `cqmax`) in properties and keyframes

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:
## Getting Started

To use the polyfill, add this script tag to the head of your document: :

```js
const supportsContainerQueries = "container" in document.documentElement.style;
if (!supportsContainerQueries) {
import("container-query-polyfill");
}
<script type="module">
if (!("container" in document.documentElement.style)) {
import("https://unpkg.com/container-query-polyfill/cqfill.modern.js");
}
</script>
```

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:
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.

> **Note**
> 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.

```html
<script src="https://unpkg.com/container-query-polyfill/cqfill.iife.min.js"></script>
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:

```css
@supports not (container-type: inline-size) {
.container,
footer {
display: none;
}

.loader {
display: flex;
}
}
```

## Browser support
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.

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+.
> **Note**
> 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.

## Feature support & limitations
## Limitations

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.
- **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.
- **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.
- Currently, there is no support for Shadow DOM, or functions like `calc(...)` in container conditions. Your contribution would be welcome!

(These decisions _can_ be revisited if they pose a significant hurdle and there is a good way to implement them. Please open an issue!)
## Supporting browsers without `:where()`

- Both the old CQ syntax as well as the new syntax are supported:
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:

<table>
<tr>
<td> Before </td> <td> After </td>
</tr>
<tr>
<td>

```css
/* These are all equivalent */
@container (min-width: 200px) {
/* ... */
}
@container (width >= 200px) {
/* ... */
}
@container size(width >= 200px) {
/* ... */
#foo {
/* ... */
}

.bar {
/* ... */
}

#foo,
.bar {
/* ... */
}

ul > li {
/* ... */
}

::before {
/* ... */
}
}
```

- Boolean operations (`and`, `or` and `not`) are supported.
- 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.
- 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`.
- Container Queries will not work when nested inside a Media Query. For now, the polyfill only supports top-level CQs.
- Container Query thresholds can only be specified using pixels.
- 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.
- CQs inside ShadowDOM are not supported yet.
- Don’t do weird interspersed comments, okay? Like `@container /* here’s a comment! */ (min-width: 1px) { ... }`. Just don’t.
</td>
<td>
```css
@container (min-width: 200px) {
#foo:not(.container-query-polyfill) {
/* ... */
}

## Building & Testing
.bar:not(.container-query-polyfill) {
/_ ... _/
}

This project uses [esbuild] to bundle the project, which is automatically installed via npm. To build the polyfill, run:
#foo:not(.container-query-polyfill),
.bar:not(.container-query-polyfill) {
/_ ... _/
}

```
npm run build
```
ul > li:not(.container-query-polyfill) {
/_ ... _/
}

To run the tests, run
:not(.container-query-polyfill)::before {
/_ ... _/
}
}

```
npm run serve
```
</td>
</tr>
</table>

and open your browser at `http://127.0.0.1:8081/tests`.
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.

---
## ResizeObserver Loop Errors

License Apache-2.0

[mdn]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries
[resizeobserver]: https://caniuse.com/resizeobserver
[mutationobserver]: https://caniuse.com/mutationobserver
[esbuild]: https://esbuild.github.io/
[is selector]: https://caniuse.com/css-matches-pseudo
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.
```
Loading