diff --git a/packages/base-styles/_animations.scss b/packages/base-styles/_animations.scss index 87e5f035f46a6c..e5bbf863757356 100644 --- a/packages/base-styles/_animations.scss +++ b/packages/base-styles/_animations.scss @@ -36,11 +36,6 @@ @include reduce-motion("animation"); } -@mixin editor-canvas-resize-animation() { - transition: all 0.4s cubic-bezier(0.46, 0.03, 0.52, 0.96); - @include reduce-motion("transition"); -} - // Deprecated @mixin edit-post__fade-in-animation($speed: 0.08s, $delay: 0s) { @warn "The `edit-post__fade-in-animation` mixin is deprecated. Use `animation__fade-in` instead."; diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 1395b5c0a437d3..9e924cb79bace1 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -4,5 +4,4 @@ iframe[name="editor-canvas"] { height: 100%; display: block; background-color: transparent; - @include editor-canvas-resize-animation; } diff --git a/packages/block-editor/src/components/iframe/content.scss b/packages/block-editor/src/components/iframe/content.scss index 069274e66bdfd0..4a0e2d519f914b 100644 --- a/packages/block-editor/src/components/iframe/content.scss +++ b/packages/block-editor/src/components/iframe/content.scss @@ -5,7 +5,21 @@ .block-editor-iframe__html { transform-origin: top center; - @include editor-canvas-resize-animation; + // 400ms should match the animation speed used in iframe/index.js + $zoomOutAnimation: all 400ms cubic-bezier(0.46, 0.03, 0.52, 0.96); + + // We don't want to animate the transform of the translateX because it is used + // to "center" the canvas. Leaving it on causes the canvas to slide around in + // odd ways. + transition: $zoomOutAnimation, transform 0s scale 0s; + @include reduce-motion("transition"); + + &.zoom-out-animation { + // we only want to animate the scaling when entering zoom out. When sidebars + // are toggled, the resizing of the iframe handles scaling the canvas as well, + // and the doubled animations cause very odd animations. + transition: $zoomOutAnimation, transform 0s; + } } .block-editor-iframe__html.is-zoomed-out { @@ -13,10 +27,11 @@ $frame-size: var(--wp-block-editor-iframe-zoom-out-frame-size); $inner-height: var(--wp-block-editor-iframe-zoom-out-inner-height); $content-height: var(--wp-block-editor-iframe-zoom-out-content-height); - $prev-container-width: var(--wp-block-editor-iframe-zoom-out-prev-container-width); - - transform: scale(#{$scale}); - + $outer-container-width: var(--wp-block-editor-iframe-zoom-out-outer-container-width); + $container-width: var(--wp-block-editor-iframe-zoom-out-container-width, 100vw); + // Apply an X translation to center the scaled content within the available space. + transform: translateX(calc(( #{$outer-container-width} - #{ $container-width }) / 2 / #{$scale})); + scale: #{$scale}; background-color: $gray-300; // Chrome seems to respect that transform scale shouldn't affect the layout size of the element, diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index d234339909a5c1..b72d86ef8e0f8f 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -122,7 +122,7 @@ function Iframe( { }, [] ); const { styles = '', scripts = '' } = resolvedAssets; const [ iframeDocument, setIframeDocument ] = useState(); - const prevContainerWidthRef = useRef(); + const initialContainerWidth = useRef(); const [ bodyClasses, setBodyClasses ] = useState( [] ); const clearerRef = useBlockSelectionClearer(); const [ before, writingFlowRef, after ] = useWritingFlow(); @@ -243,7 +243,7 @@ function Iframe( { useEffect( () => { if ( ! isZoomedOut ) { - prevContainerWidthRef.current = containerWidth; + initialContainerWidth.current = containerWidth; } }, [ containerWidth, isZoomedOut ] ); @@ -298,14 +298,49 @@ function Iframe( { useEffect( () => cleanup, [ cleanup ] ); + const zoomOutAnimationClassnameRef = useRef( null ); + const handleZoomOutAnimationClassname = () => { + clearTimeout( zoomOutAnimationClassnameRef.current ); + + iframeDocument.documentElement.classList.add( 'zoom-out-animation' ); + + zoomOutAnimationClassnameRef.current = setTimeout( () => { + iframeDocument.documentElement.classList.remove( + 'zoom-out-animation' + ); + }, 400 ); // 400ms should match the animation speed used in components/iframe/content.scss + }; + + // Toggle zoom out CSS Classes only when zoom out mode changes. We could add these into the useEffect + // that controls settings the CSS variables, but then we would need to do more work to ensure we're + // only toggling these when the zoom out mode changes, as that useEffect is also triggered by a large + // number of dependencies. useEffect( () => { if ( ! iframeDocument || ! isZoomedOut ) { return; } + handleZoomOutAnimationClassname(); iframeDocument.documentElement.classList.add( 'is-zoomed-out' ); + return () => { + handleZoomOutAnimationClassname(); + iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); + }; + }, [ iframeDocument, isZoomedOut ] ); + + // Calculate the scaling and CSS variables for the zoom out canvas + useEffect( () => { + if ( ! iframeDocument || ! isZoomedOut ) { + return; + } + const maxWidth = 750; + // Note: When we initialize the zoom out when the canvas is smaller (sidebars open), + // initialContainerWidth will be smaller than the full page, and reflow will happen + // when the canvas area becomes larger due to sidebars closing. This is a known but + // minor divergence for now. + // This scaling calculation has to happen within the JS because CSS calc() can // only divide and multiply by a unitless value. I.e. calc( 100px / 2 ) is valid // but calc( 100px / 2px ) is not. @@ -314,7 +349,10 @@ function Iframe( { scale === 'default' ? ( Math.min( containerWidth, maxWidth ) - parseInt( frameSize ) * 2 ) / - prevContainerWidthRef.current + Math.max( + initialContainerWidth.current, + containerWidth + ) : scale ); @@ -336,13 +374,16 @@ function Iframe( { `${ containerWidth }px` ); iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-prev-container-width', - `${ prevContainerWidthRef.current }px` + '--wp-block-editor-iframe-zoom-out-outer-container-width', + `${ Math.max( initialContainerWidth.current, containerWidth ) }px` ); - return () => { - iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); + // iframeDocument.documentElement.style.setProperty( + // '--wp-block-editor-iframe-zoom-out-outer-container-width', + // `${ Math.max( initialContainerWidth.current, containerWidth ) }px` + // ); + return () => { iframeDocument.documentElement.style.removeProperty( '--wp-block-editor-iframe-zoom-out-scale' ); @@ -359,7 +400,7 @@ function Iframe( { '--wp-block-editor-iframe-zoom-out-container-width' ); iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-prev-container-width' + '--wp-block-editor-iframe-zoom-out-outer-container-width' ); }; }, [ @@ -460,10 +501,12 @@ function Iframe( { isZoomedOut && 'is-zoomed-out' ) } style={ { - '--wp-block-editor-iframe-zoom-out-container-width': - isZoomedOut && `${ containerWidth }px`, - '--wp-block-editor-iframe-zoom-out-prev-container-width': - isZoomedOut && `${ prevContainerWidthRef.current }px`, + '--wp-block-editor-iframe-zoom-out-outer-container-width': + isZoomedOut && + `${ Math.max( + initialContainerWidth.current, + containerWidth + ) }px`, } } > { iframe } diff --git a/packages/block-editor/src/components/iframe/style.scss b/packages/block-editor/src/components/iframe/style.scss index dcddcdf0950a45..d05be2f3977b9f 100644 --- a/packages/block-editor/src/components/iframe/style.scss +++ b/packages/block-editor/src/components/iframe/style.scss @@ -9,9 +9,10 @@ } .block-editor-iframe__scale-container.is-zoomed-out { - $container-width: var(--wp-block-editor-iframe-zoom-out-container-width, 100vw); - $prev-container-width: var(--wp-block-editor-iframe-zoom-out-prev-container-width, 100vw); - width: $prev-container-width; - // This is to offset the movement of the iframe when we open sidebars - margin-left: calc(-1 * (#{$prev-container-width} - #{$container-width}) / 2); + $outer-container-width: var(--wp-block-editor-iframe-zoom-out-outer-container-width, 100vw); + width: $outer-container-width; + // Position the iframe so that it is always aligned with the right side so that + // the scrollbar is always visible on the right side + position: absolute; + right: 0; } diff --git a/test/e2e/specs/site-editor/zoom-out.spec.js b/test/e2e/specs/site-editor/zoom-out.spec.js index 6ede9014d2a303..2aabee07d18785 100644 --- a/test/e2e/specs/site-editor/zoom-out.spec.js +++ b/test/e2e/specs/site-editor/zoom-out.spec.js @@ -27,8 +27,8 @@ test.describe( 'Zoom Out', () => { // Check that the html is scaled. await expect( html ).toHaveCSS( - 'transform', - 'matrix(0.67, 0, 0, 0.67, 0, 0)' + 'scale', + new RegExp( /0\.[5-8][0-9]*/, 'i' ) ); const iframeRect = await iframe.boundingBox(); const htmlRect = await html.boundingBox();