Skip to content

Commit

Permalink
Site Editor: Improve the frame animation (#60363)
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad authored Apr 2, 2024
1 parent 3700871 commit 3b5ad6d
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 28 deletions.
1 change: 0 additions & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ jobs:
- name: Docker debug information
run: |
docker -v
docker-compose -v
- name: General debug information
run: |
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@react-spring/web": "^9.4.5",
"@wordpress/a11y": "file:../a11y",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/blob": "file:../blob",
Expand Down
122 changes: 122 additions & 0 deletions packages/edit-site/src/components/layout/animation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* External dependencies
*/
import { Controller } from '@react-spring/web';

/**
* WordPress dependencies
*/
import { useLayoutEffect, useMemo, useRef } from '@wordpress/element';

function getAbsolutePosition( element ) {
return {
top: element.offsetTop,
left: element.offsetLeft,
};
}

const ANIMATION_DURATION = 300;

/**
* Hook used to compute the styles required to move a div into a new position.
*
* The way this animation works is the following:
* - It first renders the element as if there was no animation.
* - It takes a snapshot of the position of the block to use it
* as a destination point for the animation.
* - It restores the element to the previous position using a CSS transform
* - It uses the "resetAnimation" flag to reset the animation
* from the beginning in order to animate to the new destination point.
*
* @param {Object} $1 Options
* @param {*} $1.triggerAnimationOnChange Variable used to trigger the animation if it changes.
*/
function useMovingAnimation( { triggerAnimationOnChange } ) {
const ref = useRef();

// Whenever the trigger changes, we need to take a snapshot of the current
// position of the block to use it as a destination point for the animation.
const { previous, prevRect } = useMemo(
() => ( {
previous: ref.current && getAbsolutePosition( ref.current ),
prevRect: ref.current && ref.current.getBoundingClientRect(),
} ),
// eslint-disable-next-line react-hooks/exhaustive-deps
[ triggerAnimationOnChange ]
);

useLayoutEffect( () => {
if ( ! previous || ! ref.current ) {
return;
}

// We disable the animation if the user has a preference for reduced
// motion.
const disableAnimation = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;

if ( disableAnimation ) {
return;
}

const controller = new Controller( {
x: 0,
y: 0,
width: prevRect.width,
height: prevRect.height,
config: { duration: ANIMATION_DURATION },
onChange( { value } ) {
if ( ! ref.current ) {
return;
}
let { x, y, width, height } = value;
x = Math.round( x );
y = Math.round( y );
width = Math.round( width );
height = Math.round( height );
const finishedMoving = x === 0 && y === 0;
ref.current.style.transformOrigin = 'center center';
ref.current.style.transform = finishedMoving
? null // Set to `null` to explicitly remove the transform.
: `translate3d(${ x }px,${ y }px,0)`;
ref.current.style.width = finishedMoving
? null
: `${ width }px`;
ref.current.style.height = finishedMoving
? null
: `${ height }px`;
},
} );

ref.current.style.transform = undefined;
const destination = ref.current.getBoundingClientRect();

const x = Math.round( prevRect.left - destination.left );
const y = Math.round( prevRect.top - destination.top );
const width = destination.width;
const height = destination.height;

controller.start( {
x: 0,
y: 0,
width,
height,
from: { x, y, width: prevRect.width, height: prevRect.height },
} );

return () => {
controller.stop();
controller.set( {
x: 0,
y: 0,
width: prevRect.width,
height: prevRect.height,
} );
};
}, [ previous, prevRect ] );

return ref;
}

export default useMovingAnimation;
39 changes: 12 additions & 27 deletions packages/edit-site/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ import { useCommonCommands } from '../../hooks/commands/use-common-commands';
import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands';
import { useIsSiteEditorLoading } from './hooks';
import useLayoutAreas from './router';
import useMovingAnimation from './animation';

const { useCommands } = unlock( coreCommandsPrivateApis );
const { useCommandContext } = unlock( commandsPrivateApis );
const { useGlobalStyle } = unlock( blockEditorPrivateApis );

const ANIMATION_DURATION = 0.5;
const ANIMATION_DURATION = 0.3;

export default function Layout() {
// This ensures the edited entity id and type are initialized properly.
Expand Down Expand Up @@ -114,7 +115,10 @@ export default function Layout() {
const isEditorLoading = useIsSiteEditorLoading();
const [ isResizableFrameOversized, setIsResizableFrameOversized ] =
useState( false );
const { areas, widths } = useLayoutAreas();
const { key: routeKey, areas, widths } = useLayoutAreas();
const animationRef = useMovingAnimation( {
triggerAnimationOnChange: canvasMode + '__' + routeKey,
} );

// This determines which animation variant should apply to the header.
// There is also a `isDistractionFreeHovering` state that gets priority
Expand Down Expand Up @@ -239,7 +243,9 @@ export default function Layout() {
} }
transition={ {
type: 'tween',
duration: disableMotion ? 0 : 0.2,
duration: disableMotion
? 0
: ANIMATION_DURATION,
ease: 'easeOut',
} }
>
Expand Down Expand Up @@ -315,36 +321,15 @@ export default function Layout() {
<div className="edit-site-layout__canvas-container">
{ canvasResizer }
{ !! canvasSize.width && (
<motion.div
whileHover={
canvasMode === 'view'
? {
scale: 1.005,
transition: {
duration: disableMotion
? 0
: 0.5,
ease: 'easeOut',
},
}
: {}
}
initial={ false }
layout="position"
<div
className={ classnames(
'edit-site-layout__canvas',
{
'is-right-aligned':
isResizableFrameOversized,
}
) }
transition={ {
type: 'tween',
duration: disableMotion
? 0
: ANIMATION_DURATION,
ease: 'easeOut',
} }
ref={ animationRef }
>
<ErrorBoundary>
<ResizableFrame
Expand Down Expand Up @@ -373,7 +358,7 @@ export default function Layout() {
{ areas.preview }
</ResizableFrame>
</ErrorBoundary>
</motion.div>
</div>
) }
</div>
) }
Expand Down
6 changes: 6 additions & 0 deletions packages/edit-site/src/components/layout/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default function useLayoutAreas() {
if ( path === '/page' ) {
const isListLayout = layout === 'list' || ! layout;
return {
key: 'pages-list',
areas: {
content: <PagePages />,
preview: isListLayout && (
Expand Down Expand Up @@ -62,6 +63,7 @@ export default function useLayoutAreas() {
// Regular other post types
if ( postType && postId ) {
return {
key: 'page',
areas: {
preview: <Editor isLoading={ isSiteEditorLoading } />,
mobile:
Expand All @@ -76,6 +78,7 @@ export default function useLayoutAreas() {
if ( path === '/wp_template' ) {
const isListLayout = isCustom !== 'true' && layout === 'list';
return {
key: 'templates-list',
areas: {
content: (
<PageTemplatesTemplateParts
Expand All @@ -101,6 +104,7 @@ export default function useLayoutAreas() {
if ( path === '/wp_template_part/all' ) {
const isListLayout = isCustom !== 'true' && layout === 'list';
return {
key: 'template-parts',
areas: {
content: (
<PageTemplatesTemplateParts
Expand All @@ -125,6 +129,7 @@ export default function useLayoutAreas() {
// Patterns
if ( path === '/patterns' ) {
return {
key: 'patterns',
areas: {
content: <PagePatterns />,
mobile: <PagePatterns />,
Expand All @@ -134,6 +139,7 @@ export default function useLayoutAreas() {

// Fallback shows the home page preview
return {
key: 'default',
areas: {
preview: <Editor isLoading={ isSiteEditorLoading } />,
mobile:
Expand Down
2 changes: 2 additions & 0 deletions packages/edit-site/src/components/layout/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
position: relative;
flex-grow: 1;
z-index: z-index(".edit-site-layout__canvas-container");
// When animating the frame size can exceed its container size.
overflow: visible;

&.is-resizing::after {
// This covers the whole content which ensures mouse up triggers
Expand Down

0 comments on commit 3b5ad6d

Please sign in to comment.