https://github.com/paperstrike/stylingadvancedhtmlelement
Dynamically creating custom elements without the fear of FOUC.
https://github.com/paperstrike/stylingadvancedhtmlelement
custom-elements fouc web-components
Last synced: 28 days ago
JSON representation
Dynamically creating custom elements without the fear of FOUC.
- Host: GitHub
- URL: https://github.com/paperstrike/stylingadvancedhtmlelement
- Owner: PaperStrike
- License: mit
- Created: 2020-08-06T17:27:11.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2020-08-07T12:38:22.000Z (almost 6 years ago)
- Last Synced: 2024-10-18T08:53:42.988Z (over 1 year ago)
- Topics: custom-elements, fouc, web-components
- Language: JavaScript
- Homepage:
- Size: 4.88 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Dynamical Custom Element Without FOUC
It has long been a problem that you have to either write css **inside** js code or import them from external file and bear a [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) (flash of unstyled content) when **dynamically creating custom elements**.
> **PS:** I found the issue by googling it for 10 minutes, so don't expect me to be responsible for the authenticity of the 'either...or...' sentence.
but there is a way.
## Idea
The idea to the problem is almost the same as to solve FOUC of other kinds by operating related element: **hide before done**.
### 1. Write a defalut style for the unstyled element.
The prefered CSS is like:
```css
:host {
position: absolute;
visibility: hidden;
}
```
- `absolute` makes the element removed from the normal document flow, so that it won't occupy the space it should not.
- Hide the element before style is loaded.
**Q ?** Why you would like to remove my element from the normal document flow?
**A !** Not liking FOUC of some kinds, which affects the whole page, the FOUC of custom elements only affects the element **itself**. So the solution to them has a little difference, too. Normal FOUC may be solved by hide the whole page before CSS is ready, while custom-element-FOUC **should not** do a such thing. In this case, keep the element in the normal flow may **occupy the wrong place**, causing **twice** "FOOC" (flash of other content). (Remember we are dynamically adding elements.)
**Q ?** Why don't you use `display: none` directly? It's pretty clean.
**A !** Simply set `display` to `none` will surely remove the element from the normal document flow and hide the whole thing, but browsers today [may not load contents of it and its descendants as well](https://stackoverflow.com/questions/12158540/does-displaynone-prevent-an-image-from-loading), which means images might **not be ready** for the next step, appearing. So I suggest the combination of `visibility` and `position`.
### 2. Apply the style when ready.
Now it's done. Looks simple, like many answers on Stack Overflow before, right? So, it's just adapting a **Custom Element** version:
## Usage
Copy the *sample code* below or download [`export.js`](https://github.com/PaperFlu/StylingAdvancedHTMLElement/raw/master/export.js).
```javascript
class Style {
status = 'unloaded'; // unloaded, loading, complete.
url = '';
text = '';
applyQueue = [];
constructor(url) {
this.url = url;
this.update();
}
async update() {
if (this.status === 'loading') return;
this.status = 'loading';
await fetch(this.url).then(r => r.text()).then((text) => {
this.text = text;
this.status = 'complete';
for (const ele of this.applyQueue) {
ele.styleEle.textContent = text;
};
}).catch((error) => {
this.status = 'unloaded';
throw error;
});
}
};
class StylingAdvancedHTMLElement extends HTMLElement {
static styleFileUrl;
static initialStyleText = `
:host {
position: absolute !important;
visibility: hidden !important;
}
`;
static styleList = [];
styleEle = document.createElement('style');
constructor() {
super();
const shadow = this.attachShadow({
mode: 'open',
});
const {
styleFileUrl,
initialStyleText,
styleList,
} = this.constructor;
let style = styleList.find(style => styleFileUrl === style.url);
if (!style) {
style = new Style(styleFileUrl);
styleList.push(style);
};
let styleText;
if (style.status !== 'complete') {
styleText = initialStyleText;
style.applyQueue.push(this);
} else {
styleText = style.text;
};
this.styleEle.textContent = styleText;
shadow.prepend(this.styleEle);
}
};
```
> Edit the code or `export.js` to fit your need.
Open your project and paste, or import `StylingAdvancedHTMLElement` from `export.js` if you don't want my freely written code ruins yours **(recommanded)**. Then, find all the string representing class `HTMLElement`, or say which your custom element's constructor extends from, **replace** them with `StylingAdvancedHTMLElement`. It will be like:
```javascript
// Before:
class StupidToggler extends HTMLElement {
constructor() {
super();
}
};
// After:
class StupidToggler extends StylingAdvancedHTMLElement {
constructor() {
super();
}
};
```
Delete your previous ``-or-``-creating code, along with `attachShadow` call. Set the url of the CSS file as a **static** property, `styleFileUrl`. A `fetch` will be used later to fetch the CSS.
```javascript
// Before:
class StupidToggler extends StylingAdvancedHTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: open});
const styleEle = document.createElement('link');
styleEle.setAttribute('rel', 'stylesheet');
styleEle.setAttribute('href', './stupid-toggler.css');
shadow.appendChild(styleEle);
}
};
// After:
class StupidToggler extends StylingAdvancedHTMLElement {
static styleFileUrl = './styles/stupid-toggler.css';
constructor() {
super();
}
};
// Or you may have a function to get URLs.
class StupidToggler extends StylingAdvancedHTMLElement {
static styleFileUrl = getUrl('stupid-toggler.css');
constructor() {
super();
// Use this.shadowRoot in future code to get equal access as the `shadow` parameter.
}
};
```
Everything is ready but your habit now.
## Troubleshooting and Suggestions
Trouble? Try openning an issue.
**Q ?** I found your code can be improved!
**A ?** Improve and pull request, we can discuss it!
## License
MIT Licensed