Skip to content

Commit

Permalink
Refactor zoom-out iframe scale
Browse files Browse the repository at this point in the history
  • Loading branch information
ajlende committed Mar 6, 2024
1 parent 272f402 commit ec88873
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 71 deletions.
73 changes: 29 additions & 44 deletions packages/block-editor/src/components/iframe/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { useBlockSelectionClearer } from '../block-selection-clearer';
import { useWritingFlow } from '../writing-flow';
import { getCompatibilityStyles } from './get-compatibility-styles';
import { store as blockEditorStore } from '../../store';
import calculateScale from '../../utils/calculate-scale';

function bubbleEvent( event, Constructor, frame ) {
const init = {};

Expand Down Expand Up @@ -104,25 +104,21 @@ function Iframe( {
contentRef,
children,
tabIndex = 0,
shouldZoom = false,
scale = 1,
frameSize = 0,
readonly,
forwardedRef: ref,
title = __( 'Editor canvas' ),
...props
} ) {
const { resolvedAssets, isPreviewMode, isZoomOutMode } = useSelect(
( select ) => {
const { getSettings, __unstableGetEditorMode } =
select( blockEditorStore );
const settings = getSettings();
return {
resolvedAssets: settings.__unstableResolvedAssets,
isPreviewMode: settings.__unstableIsPreviewMode,
isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
};
},
[]
);
const { resolvedAssets, isPreviewMode } = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
const settings = getSettings();
return {
resolvedAssets: settings.__unstableResolvedAssets,
isPreviewMode: settings.__unstableIsPreviewMode,
};
}, [] );
const { styles = '', scripts = '' } = resolvedAssets;
const [ iframeDocument, setIframeDocument ] = useState();
const [ bodyClasses, setBodyClasses ] = useState( [] );
Expand All @@ -133,24 +129,6 @@ function Iframe( {
{ height: contentHeight, width: contentWidth },
] = useResizeObserver();

// When zoom-out mode is enabled, the iframe is scaled down to fit the
// content within the viewport.
// At 1000px wide, the iframe is scaled to 45%.
// At 400px wide, the iframe is scaled to 90%.
const scale =
isZoomOutMode && shouldZoom
? calculateScale(
{
maxWidth: 1000,
minWidth: 400,
maxScale: 0.45,
minScale: 0.9,
},
contentWidth
)
: 1;
const frameSize = isZoomOutMode ? 100 : 0;

const setRef = useRefEffect( ( node ) => {
node._load = () => {
setIframeDocument( node.contentDocument );
Expand Down Expand Up @@ -276,25 +254,32 @@ function Iframe( {

useEffect( () => cleanup, [ cleanup ] );

// We need to counter the margin created by scaling the iframe. If the scale
// is e.g. 0.45, then the top + bottom margin is 0.55 (1 - scale). Just the
// top or bottom margin is 0.55 / 2 ((1 - scale) / 2).
const marginFromScaling = ( contentHeight * ( 1 - scale ) ) / 2;

useEffect( () => {
if ( iframeDocument && scale !== 1 ) {
iframeDocument.documentElement.style.transform = `scale( ${ scale } )`;
if ( ! iframeDocument ) {
return;
}

const _scale =
typeof scale === 'function'
? scale( contentWidth, contentHeight )
: scale;

if ( _scale !== 1 ) {
// Hack to get proper margins when scaling the iframe document.
const bottomFrameSize = frameSize - contentHeight * ( 1 - _scale );

iframeDocument.documentElement.style.transform = `scale( ${ _scale } )`;
iframeDocument.documentElement.style.marginTop = `${ frameSize }px`;
iframeDocument.documentElement.style.marginBottom = `${
-marginFromScaling * 2 + frameSize
}px`;
// TODO: `marginBottom` doesn't work in Firefox. We need another way to do this.
iframeDocument.documentElement.style.marginBottom = `${ bottomFrameSize }px`;

return () => {
iframeDocument.documentElement.style.transform = '';
iframeDocument.documentElement.style.marginTop = '';
iframeDocument.documentElement.style.marginBottom = '';
};
}
}, [ scale, frameSize, marginFromScaling, iframeDocument ] );
}, [ scale, frameSize, contentHeight, contentWidth, iframeDocument ] );

// Make sure to not render the before and after focusable div elements in view
// mode. They're only needed to capture focus in edit mode.
Expand Down
20 changes: 0 additions & 20 deletions packages/block-editor/src/utils/calculate-scale.js

This file was deleted.

23 changes: 16 additions & 7 deletions packages/edit-site/src/components/block-editor/editor-canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import {
FOCUSABLE_ENTITIES,
NAVIGATION_POST_TYPE,
} from '../../utils/constants';
import { computeIFrameScale } from '../../utils/math';

const { EditorCanvas: EditorCanvasRoot } = unlock( editorPrivateApis );

function EditorCanvas( { enableResizing, settings, children, ...props } ) {
const { hasBlocks, isFocusMode, templateType, canvasMode } = useSelect(
( select ) => {
const { getBlockCount } = select( blockEditorStore );
const { hasBlocks, isFocusMode, templateType, canvasMode, isZoomOutMode } =
useSelect( ( select ) => {
const { getBlockCount, __unstableGetEditorMode } =
select( blockEditorStore );
const { getEditedPostType, getCanvasMode } = unlock(
select( editSiteStore )
);
Expand All @@ -37,12 +39,11 @@ function EditorCanvas( { enableResizing, settings, children, ...props } ) {
return {
templateType: _templateType,
isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ),
isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
canvasMode: getCanvasMode(),
hasBlocks: !! getBlockCount(),
};
},
[]
);
}, [] );
const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
const [ isFocused, setIsFocused ] = useState( false );

Expand Down Expand Up @@ -110,7 +111,15 @@ function EditorCanvas( { enableResizing, settings, children, ...props } ) {
renderAppender={ showBlockAppender }
styles={ styles }
iframeProps={ {
shouldZoom: true,
scale: isZoomOutMode
? ( contentWidth ) =>
computeIFrameScale(
{ width: 1000, scale: 0.45 },
{ width: 400, scale: 0.9 },
contentWidth
)
: undefined,
frameSize: isZoomOutMode ? 100 : undefined,
className: classnames(
'edit-site-visual-editor__editor-canvas',
{
Expand Down
93 changes: 93 additions & 0 deletions packages/edit-site/src/utils/math.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @typedef {Object} WPPoint
* @property {number} x The horizontal coordinate.
* @property {number} y The vertical coordinate.
*/

/**
* Clamps a value to a range. Uses the CSS `clamp()` ordering for arguments.
*
* @param {number} min Minimum range.
* @param {number} value Value to clamp.
* @param {number} max Maximum range.
*
* @return {number} Clamped value.
*/
function clamp( min, value, max ) {
return Math.max( min, Math.min( value, max ) );
}

/**
* Evaluates a linear function passing through two points at the given x value.
*
* Example:
* f(x)
* │ ╲
* │ * (p0)
* │ ╲
* │ ╲
* │ (p1) *
* │ ╲
* └──────────── x
*
* @param {WPPoint} p0 First point.
* @param {WPPoint} p1 Second point.
* @param {number} x Value to evaluate at.
*
* @return {number} Result of the two-point linear function at x.
*/
function twoPointLinearFn( p0, p1, x ) {
return ( ( p1.y - p0.y ) / ( p1.x - p0.x ) ) * ( x - p0.x ) + p0.y;
}

/**
* Evaluates a two-point linear function at a given x value, clamped to the range of the points.
*
* Example:
* f(x)
* │ ───* (p0)
* │ ╲
* │ ╲
* │ (p1) *───
* └──────────── x
*
* @param {WPPoint} p0 First point.
* @param {WPPoint} p1 Second point.
* @param {number} x Value to evaluate at.
*
* @return {number} Result of the two-point linear function clamped to the range of the points.
*/
function clampedTwoPointLinearFn( p0, p1, x ) {
return clamp(
Math.min( p0.y, p1.y ),
twoPointLinearFn( p0, p1, x ),
Math.max( p0.y, p1.y )
);
}

/**
* Computes the iframe scale using a start and end width/scale pair and the current width.
*
* The scale is clamped outside the points and is linearly interpolated between.
*
* Example:
* scale
* │ ───* (start)
* │ ╲
* │ ╲
* │ (end) *───
* └──────────── width
*
* @param {Object} start First width and scale pair.
* @param {Object} end Second width and scale pair.
* @param {number} currentWidth Current width.
*
* @return {number} The scale of the current width between the two points.
*/
export function computeIFrameScale( start, end, currentWidth ) {
return clampedTwoPointLinearFn(
{ x: start.width, y: start.scale },
{ x: end.width, y: end.scale },
currentWidth
);
}

0 comments on commit ec88873

Please sign in to comment.