Dealing with a flash of unstyled content issue. I've got a chat widget that is injected onto users' pages. So that I don't clash with existing styles. I'm rendering in an iframe using react-frame-component
. This works pretty nicely, but one problem I have is with the CSS.
react-frame-component
accepts a prop head
that you can use to pass a link to a stylesheet. Using MiniCSSExtractPlugin, I am able to get my css into a separate file that I can then link to:
render(props, state) {
return (
<Frame
head={<link rel="stylesheet" type="text/css" href="style.css" />}
scrolling="no"
>
<ChatWidget />
</Frame>
);
}
With this, I get the dreaded flash of unstyled content. I think it's because everything including <ChatWidget>
renders before the css can be fully downloaded.
Is there a way I can ensure the CSS is downloaded before everything renders?
Also, not sure if it matters, but I'm using Preact.
I ended up writing my own iframe component, using some tips from react-frame-component
but writing it in a way I could be sure I had control over how it rendered. Here's what my iframe component looks like (note this uses Preact, which has some minor differences from React - it would be very easy to replicate this in React):
import {h, Component, createRef} from 'preact';
import {createPortal} from 'preact/compat';
import {cssLink} from '../utilities';
export default class Frame extends Component {
iframeNode = createRef();
iframeTest = document.createElement('iframe');
static initialContent() {
return `<!DOCTYPE html><head><link rel="stylesheet" type="text/css" href="${cssLink}"></head><body></body></html>`;
}
componentDidMount() {
this.iframeNode.addEventListener('load', this.handleLoad);
}
handleLoad = () => {
this.iframeRoot = this.iframeNode.contentDocument.body;
this.forceUpdate();
};
sourceProp() {
const canUseSrcDoc = 'srcdoc' in this.iframeTest;
if (canUseSrcDoc) {
return {
srcdoc: Frame.initialContent(),
};
}
// inserting Frame.initialContent() as the src is a hack for older browsers
// to allow inserting our stylesheet before the children render, preventing
// flashes of unstyled content
return {
src: `javascript: '${Frame.initialContent()}'`,
};
}
render(props) {
const {children, ...rest} = props;
return (
<iframe
{...rest}
{...this.sourceProp()}
ref={ref => (this.iframeNode = ref)}
>
{this.iframeRoot && createPortal(children, this.iframeRoot)}
</iframe>
);
}
}