Skip to content

Commit 1c224c9

Browse files
committed
Canvas preview scale
Dynamically adjust the scale of the iframe (preview) based on the view port / container size and actively listens for resize events to respond. Ideally, the source templates should be handling any responsive design problems, instead of going around it here. cc: @wpscholar
1 parent f0d1939 commit 1c224c9

File tree

4 files changed

+138
-24
lines changed

4 files changed

+138
-24
lines changed

build/2.7.4/onboarding.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-media-utils', 'wp-preferences', 'wp-url'), 'version' => '52ccb128c4a624f21547');
1+
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-media-utils', 'wp-preferences', 'wp-url'), 'version' => '987507288a1b0cd041df');

build/2.7.4/onboarding.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/components/Iframe/Iframe.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import classNames from 'classnames';
2+
import { forwardRef } from '@wordpress/element';
23

3-
const Iframe = ( {
4+
const Iframe = forwardRef( ( {
45
title,
56
src,
67
width = 400,
@@ -10,7 +11,7 @@ const Iframe = ( {
1011
viewportHeight = 1000,
1112
className,
1213
...props
13-
} ) => {
14+
}, ref ) => {
1415
return (
1516
<div
1617
className={ classNames(
@@ -35,9 +36,10 @@ const Iframe = ( {
3536
transform: `scale(${ viewportScale })`,
3637
} }
3738
{ ...props }
39+
ref={ ref }
3840
/>
3941
</div>
4042
);
41-
};
43+
} );
4244

4345
export default Iframe;

src/app/steps/Canvas/Preview.js

Lines changed: 130 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* global ResizeObserver */
2+
import { useRef, useState, useEffect } from '@wordpress/element';
13
import { useSelect } from '@wordpress/data';
24
import { nfdOnboardingStore } from '@/data/store';
35
import { Iframe } from '@/components';
@@ -14,7 +16,17 @@ const Preview = () => {
1416
selectedHomepage: select( nfdOnboardingStore ).getSelectedHomepage(),
1517
};
1618
} );
17-
19+
20+
const refMap = useRef( {
21+
previewContainer: null,
22+
previewContainerResizeObserver: null,
23+
iframeElement: null,
24+
previousIframeDimensions: {
25+
width: 0,
26+
height: 0,
27+
},
28+
} );
29+
1830
const StatusOverlay = () => {
1931
const appHeaderHeight = document.querySelector( '.nfd-onboarding-header' )?.offsetHeight || 0;
2032

@@ -29,7 +41,7 @@ const Preview = () => {
2941
<Spinner
3042
variant="primary"
3143
size="8"
32-
/>
44+
/>
3345
</div>
3446
);
3547
}
@@ -47,9 +59,10 @@ const Preview = () => {
4759
// eslint-disable-next-line react-hooks/exhaustive-deps
4860
}, [ selectedHomepage ] );
4961

62+
// Calculate the iframe height.
5063
const calculateIframeHeight = () => {
51-
// Get the height from the iframe
52-
const iframeDoc = window.frames[ `nfd-onboarding-${ preview.slug }` ]?.document;
64+
// Get the iframe content height.
65+
const iframeDoc = refMap.current.iframeElement?.contentWindow?.document;
5366
if ( iframeDoc ) {
5467
return iframeDoc.documentElement.scrollHeight;
5568
}
@@ -58,24 +71,115 @@ const Preview = () => {
5871
return window.innerHeight - appHeaderHeight;
5972
};
6073

61-
const iframeOnLoad = () => {
62-
setIframeHeight( calculateIframeHeight() );
74+
/**
75+
* Calculate the iframe content (preview) scale based on the parent container width.
76+
*/
77+
const calculateIframeContentScale = () => {
78+
const previewContainer = refMap.current.previewContainer;
79+
const iframeDoc = refMap.current.iframeElement?.contentWindow?.document;
80+
if ( ! previewContainer || ! iframeDoc ) {
81+
return;
82+
}
83+
84+
const containerWidth = previewContainer.offsetWidth;
85+
if ( containerWidth === 0 ) {
86+
return;
87+
}
88+
89+
// Set the preview content scale by changing its zoom property.
90+
const setContentScale = ( value ) => {
91+
iframeDoc.documentElement.style.setProperty( 'zoom', value, 'important' );
92+
};
6393

6494
/**
65-
* Custom styles to add spacing around the preview.
95+
* The scale breakpoints map.
96+
* The order of the breakpoints is important.
97+
* The first breakpoint that matches the container width will be used.
6698
*/
67-
const iframeDoc = document.getElementById( `nfd-onboarding-${ preview.slug }-selected` )?.contentWindow?.document;
99+
const scaleBreakpointsMap = [
100+
{ minWidth: 1440, scale: 1 },
101+
{ minWidth: 1280, scale: 0.95 },
102+
{ minWidth: 1024, scale: 0.9 },
103+
{ minWidth: 768, scale: 0.85 },
104+
{ minWidth: 480, scale: 0.8 },
105+
];
106+
107+
// Find the scale breakpoint that matches the container width.
108+
const scaleBreakpoint = scaleBreakpointsMap.find( ( breakpoint ) => containerWidth > breakpoint.minWidth );
109+
if ( scaleBreakpoint ) {
110+
setContentScale( scaleBreakpoint.scale );
111+
} else {
112+
setContentScale( 1 );
113+
}
114+
};
115+
116+
/**
117+
* Dynamically control the iframe content scale and height.
118+
* Runs on iframe load/re-render.
119+
*
120+
* Don't let the code scare you, just follow the comments and you'll be fine.
121+
*/
122+
const dynamicallyControlIframeScale = () => {
123+
// Disconnect lingering resize observer.
124+
if ( refMap.current.previewContainerResizeObserver ) {
125+
refMap.current.previewContainerResizeObserver.disconnect();
126+
}
127+
// Observe the preview container for resize events (console, user window resize, etc).
128+
refMap.current.previewContainerResizeObserver = new ResizeObserver( ( entries ) => {
129+
entries.forEach( ( entry ) => {
130+
// Get the new width and height values after the resize event.
131+
const { width: newWidth, height: newHeight } = entry.contentRect;
132+
133+
// Check which values have changed (width, height, both, none).
134+
const widthHasChanged = newWidth !== refMap.current.previousIframeDimensions.width;
135+
const heightHasChanged = newHeight !== refMap.current.previousIframeDimensions.height;
136+
137+
// Update the previous dimensions to the new values.
138+
refMap.current.previousIframeDimensions = {
139+
width: newWidth,
140+
height: newHeight,
141+
};
142+
143+
// If the width has changed, calculate the iframe content scale.
144+
if ( widthHasChanged ) {
145+
calculateIframeContentScale();
146+
}
147+
148+
// If the height has changed, set the iframe height.
149+
if ( heightHasChanged ) {
150+
setIframeHeight( calculateIframeHeight() );
151+
}
152+
} );
153+
} );
154+
refMap.current.previewContainerResizeObserver.observe( refMap.current.previewContainer );
155+
};
156+
157+
// On unmount: Disconnect the resize observer.
158+
useEffect( () => {
159+
return () => {
160+
if ( refMap.current.previewContainerResizeObserver ) {
161+
refMap.current.previewContainerResizeObserver.disconnect();
162+
}
163+
};
164+
}, [] );
165+
166+
const iframeOnLoad = () => {
167+
dynamicallyControlIframeScale();
168+
169+
const iframeDoc = refMap.current.iframeElement?.contentWindow?.document;
68170
if ( iframeDoc ) {
69-
// Preview margins and styles
171+
/**
172+
* The styles below add the spacing around the preview.
173+
*/
70174
// <html> styles
71-
iframeDoc.documentElement.style.setProperty("padding", "40px 25px 110px", "important");
72-
iframeDoc.documentElement.style.setProperty("overflow", "overlay", "important");
73-
iframeDoc.documentElement.style.setProperty("background-color", "#ECEDEE", "important");
175+
iframeDoc.documentElement.style.setProperty( 'padding', '40px 25px 110px', 'important' );
176+
iframeDoc.documentElement.style.setProperty( 'overflow', 'overlay', 'important' );
177+
iframeDoc.documentElement.style.setProperty( 'background-color', '#ECEDEE', 'important' );
74178
// <body> styles
75-
iframeDoc.body.style.setProperty("border-radius", "10px", "important");
76-
iframeDoc.body.style.setProperty("overflow", "overlay", "important");
77-
iframeDoc.body.style.setProperty("border", "1px solid #CBD5E1", "important");
78-
iframeDoc.body.style.setProperty("box-shadow", "0 0 0px 2px #c5cbd4, 0 0 25px 7px rgba(64, 77, 96, 0.1)", "important");
179+
iframeDoc.body.style.setProperty( 'border-radius', '10px', 'important' );
180+
iframeDoc.body.style.setProperty( 'overflow', 'overlay', 'important' );
181+
iframeDoc.body.style.setProperty( 'border', '1px solid #CBD5E1', 'important' );
182+
iframeDoc.body.style.setProperty( 'box-shadow', '0 0 0px 2px #c5cbd4, 0 0 25px 7px rgba(64, 77, 96, 0.1)', 'important' );
79183
// End: Preview styles
80184
}
81185

@@ -86,9 +190,17 @@ const Preview = () => {
86190
};
87191

88192
return (
89-
<div className="nfd-onboarding-canvas-preview nfd-relative nfd-w-full">
193+
<div
194+
className="nfd-onboarding-canvas-preview nfd-relative nfd-w-full"
195+
ref={ ( el ) => {
196+
refMap.current.previewContainer = el;
197+
} }
198+
>
90199
<StatusOverlay />
91200
{ preview && <Iframe
201+
ref={ ( el ) => {
202+
refMap.current.iframeElement = el;
203+
} }
92204
title={ preview.slug }
93205
id={ `nfd-onboarding-${ preview.slug }-selected` }
94206
src={ preview.iframeSrc }
@@ -99,7 +211,7 @@ const Preview = () => {
99211
onLoad={ iframeOnLoad }
100212
tabIndex="-1"
101213
inert
102-
className="nfd-cursor-default"
214+
className="nfd-cursor-default "
103215
/> }
104216
</div>
105217
);

0 commit comments

Comments
 (0)