add fouce utilities

This commit is contained in:
Cory LaViska
2025-02-04 12:56:52 -05:00
parent b3e4c59197
commit 72a6d8544d
5 changed files with 64 additions and 3 deletions

View File

@@ -37,8 +37,30 @@ This snippet includes three parts:
Now you can [start using Web Awesome!](/docs/usage)
:::info
While convenient, autoloading may lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). The linked article describes some ways to alleviate it.
### Reducing FOUCE
While convenient, autoloading can lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). To prevent this, you can add the `wa-reduce-fouce` class to any element on the page. Make sure to include the [FOUCE style utility](/docs/utilities/fouce/#opting-in) to get the corresponding CSS.
```html
<html class="wa-reduce-fouce">
...
</html>
```
As soon as all elements are registered OR after two seconds have elapsed, the autoloader will show the page. The two-second timeout prevents blank screens from persisting on slow networks and pages that have errors.
:::details Are you using Turbo in your app?
If you're using [Turbo](https://turbo.hotwired.dev/) to serve a multi-page application (MPA) as a single page application (SPA), you might notice FOUCE when navigating from page to page. This is because Turbo renders the new page's content before the autoloader has a change to register new components.
The following function acts as a middleware to ensure components are registered _before_ the page shows, eliminating FOUCE for page-to-page navigation with Turbo.
```js
import { preventTurboFouce } from '/dist/webawesome.js';
preventTurboFouce();
```
:::
---

View File

@@ -18,3 +18,8 @@
animation: var(--wa-fouce-animation);
}
}
/* An opt in utility that hides any container with this class until the autoloader removes it */
.wa-reduce-fouce {
opacity: 0;
}

View File

@@ -50,6 +50,12 @@ export async function discover(root: Element | ShadowRoot) {
console.warn(imp.reason); // eslint-disable-line no-console
}
}
// Wait a cycle to allow the first Lit update to run
await new Promise(requestAnimationFrame);
// Dispatch an event when discovery is complete.
document.dispatchEvent(new CustomEvent('wa-discovery-complete'));
}
/**
@@ -69,3 +75,19 @@ function register(tagName: string): Promise<void> {
import(path).then(() => resolve()).catch(() => reject(new Error(`Unable to autoload <${tagName}> from ${path}`)));
});
}
/** TODO */
export function preventTurboFouce(timeout = 2000) {
document.addEventListener('turbo:before-render', async (event: CustomEvent) => {
const newBody = event.detail.newBody;
event.preventDefault();
try {
// Wait until all elements are registered or two seconds, whichever comes first
await Promise.race([discover(newBody), new Promise(resolve => setTimeout(resolve, timeout))]);
} finally {
event.detail.resume();
}
});
}

View File

@@ -3,3 +3,15 @@ import { startLoader } from './webawesome.js';
export * from './webawesome.js';
startLoader();
// Remove the `wa-reduce-fouce` class from all elements that have it after the autoloader finishes discovering the page
// OR two seconds. This helps reduce FOUCE by letting users opt in to hiding containers (or the entire page) until the
// components are ready.
Promise.race([
new Promise(resolve => document.addEventListener('wa-discovery-complete', resolve)),
new Promise(resolve => setTimeout(resolve, 2000)),
]).then(() => {
document.querySelectorAll('.wa-reduce-fouce').forEach(el => {
el.classList.remove('wa-reduce-fouce');
});
});

View File

@@ -1,5 +1,5 @@
export { registerIconLibrary, unregisterIconLibrary } from './components/icon/library.js';
export { discover, startLoader, stopLoader } from './utilities/autoloader.js';
export { discover, preventTurboFouce, startLoader, stopLoader } from './utilities/autoloader.js';
export { getBasePath, getKitCode, setBasePath, setKitCode } from './utilities/base-path.js';
export { registerTranslation } from './utilities/localize.js';