Skip to content

Commit f46f72a

Browse files
authored
docs: add deferred mount to playground ember (#3618)
No issue, but discussed in Discord. Closes # ## Changes: - Adds an intersection observer so embedded Playgrounds can optionally mount after they come into view Can be seen on this preview page: https://fix-playground-intersection.excaliburjs.pages.dev/docs/physics
1 parent 9ed7cab commit f46f72a

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

site/docs/10-physics/08-a-physics.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ const game = new Engine({
4747

4848
<PlaygroundEmbed
4949
title="Physics"
50-
code={PhysicsExample}
50+
code={PhysicsExample}
51+
mount="visible"
5152
/>
5253

5354
## Other Physics Settings
Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import * as lz from "lz-string";
1+
import * as lz from 'lz-string';
22
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
3+
import { useEffect, useRef, useState } from 'react';
34

4-
type Props = {
5+
type Props = {
56
autoplay?: boolean;
67
code: string;
78
title?: string;
8-
}
9+
mount?: 'immediately' | 'visible';
10+
};
911

10-
const style = {
12+
const rootStyle = {
1113
width: '100%',
1214
maxWidth: '640px',
1315
borderRadius: '12px',
@@ -17,24 +19,68 @@ const style = {
1719
display: 'block',
1820
height: '900px',
1921
border: 0,
20-
overflow: 'hidden',
21-
}
22+
overflow: 'hidden'
23+
};
24+
25+
const iframeStyle = {
26+
width: '100%',
27+
display: 'block',
28+
height: '100%',
29+
border: 0,
30+
overflow: 'hidden'
31+
};
32+
33+
export default (props: Props) => {
34+
const { autoplay = true, code, title, mount = 'immediately' } = props;
35+
36+
const [isReady, setIsReady] = useState(mount === 'immediately');
37+
const ref = useRef<HTMLDivElement>(null);
2238

23-
export default ({ autoplay = true, code, title }: Props) => {
2439
const params = new URLSearchParams({
2540
embed: 'true',
2641
code: lz.compressToEncodedURIComponent(code)
2742
});
2843

2944
const { siteConfig } = useDocusaurusContext();
3045
const { customFields } = siteConfig;
31-
const { playgroundUrl } = customFields;
32-
46+
const { playgroundUrl } = customFields;
47+
3348
if (autoplay) {
3449
params.set('autoplay', 'true');
3550
}
36-
51+
3752
const src = `${playgroundUrl}/?${params.toString()}`;
3853

39-
return <iframe src={src} style={style} title={title} />
40-
}
54+
useEffect(() => {
55+
if (mount !== 'visible') {
56+
return;
57+
}
58+
59+
const updateEntry = (entries: IntersectionObserverEntry[]): void => {
60+
// This is a one-way gate:
61+
// Exiting the viewpoint will _not_ unmount the Playground
62+
if (!entries.every((entry) => entry.isIntersecting)) {
63+
return;
64+
}
65+
66+
setIsReady(true);
67+
};
68+
69+
const node = ref?.current;
70+
const hasIOSupport = !!window.IntersectionObserver;
71+
72+
if (!node || !hasIOSupport) return;
73+
74+
// When 25% of the Playground is visible it is considered and mounted.
75+
const observer = new IntersectionObserver(updateEntry, { threshold: 0.25 });
76+
observer.observe(node);
77+
78+
return () => observer.disconnect();
79+
}, [mount]);
80+
81+
return (
82+
<div ref={ref} style={rootStyle}>
83+
{isReady ? <iframe src={src} style={iframeStyle} title={title} /> : null}
84+
</div>
85+
);
86+
};

0 commit comments

Comments
 (0)