-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a block reordering animation #16065
Changes from all commits
8540657
9a1f982
46c0bc8
154b30c
bd79e77
64817d5
131a22a
a5b81f2
8e16430
08dc970
b07a115
4b7ec6a
67c9904
2a6c267
1a429df
2b16333
267c7df
265548c
5d4ec9e
37bd5b2
6660d39
90de4d5
3279e11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useSpring, interpolate } from 'react-spring/web.cjs'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useState, useLayoutEffect } from '@wordpress/element'; | ||
import { useReducedMotion } from '@wordpress/compose'; | ||
|
||
/** | ||
* 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} ref Reference to the element to animate. | ||
* @param {boolean} isSelected Whether it's the current block or not. | ||
* @param {boolean} enableAnimation Enable/Disable animation. | ||
* @param {*} triggerAnimationOnChange Variable used to trigger the animation if it changes. | ||
* | ||
* @return {Object} Style object. | ||
*/ | ||
function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationOnChange ) { | ||
const prefersReducedMotion = useReducedMotion() || ! enableAnimation; | ||
const [ resetAnimation, setResetAnimation ] = useState( false ); | ||
const [ transform, setTransform ] = useState( { x: 0, y: 0 } ); | ||
|
||
const previous = ref.current ? ref.current.getBoundingClientRect() : null; | ||
useLayoutEffect( () => { | ||
if ( prefersReducedMotion ) { | ||
return; | ||
} | ||
ref.current.style.transform = 'none'; | ||
const destination = ref.current.getBoundingClientRect(); | ||
const newTransform = { | ||
x: previous ? previous.left - destination.left : 0, | ||
y: previous ? previous.top - destination.top : 0, | ||
}; | ||
ref.current.style.transform = `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; | ||
setResetAnimation( true ); | ||
setTransform( newTransform ); | ||
}, [ triggerAnimationOnChange ] ); | ||
useLayoutEffect( () => { | ||
if ( resetAnimation ) { | ||
setResetAnimation( false ); | ||
} | ||
}, [ resetAnimation ] ); | ||
const animationProps = useSpring( { | ||
from: transform, | ||
to: { | ||
x: 0, | ||
y: 0, | ||
}, | ||
reset: resetAnimation, | ||
config: { mass: 5, tension: 2000, friction: 200 }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not completely satisfied with this set yet, but we can tweak it later. |
||
immediate: prefersReducedMotion, | ||
} ); | ||
|
||
return { | ||
transformOrigin: 'center', | ||
transform: interpolate( | ||
[ | ||
animationProps.x, | ||
animationProps.y, | ||
], | ||
( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)` | ||
), | ||
zIndex: interpolate( | ||
[ | ||
animationProps.x, | ||
animationProps.y, | ||
], | ||
( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` | ||
), | ||
}; | ||
} | ||
|
||
export default useMovingAnimation; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,20 @@ | |
*/ | ||
import useMediaQuery from '../use-media-query'; | ||
|
||
/** | ||
* Whether or not the user agent is Internet Explorer. | ||
* | ||
* @type {boolean} | ||
*/ | ||
const IS_IE = window.navigator.userAgent.indexOf( 'Trident' ) >= 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a heads up that this line breaks the mobile app. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry about that. Should we maybe create a custom version of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the equivalent landed on 0.60 which was just released: facebook/react-native#23839 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, let's add the equivalent hook once we upgrade to 0.60 |
||
|
||
/** | ||
* Hook returning whether the user has a preference for reduced motion. | ||
* | ||
* @return {boolean} Reduced motion preference value. | ||
*/ | ||
const useReducedMotion = | ||
process.env.FORCE_REDUCED_MOTION ? | ||
process.env.FORCE_REDUCED_MOTION || IS_IE ? | ||
() => true : | ||
() => useMediaQuery( '(prefers-reduced-motion: reduce)' ); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module is 47kb minified before gzip, and we're now duplicating it across two of our packages.
At what point should we consider extracting it as a common vendor dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know :) What do you think. I don't mind extracting it but it feels very risky in terms of BC as I expect breaking changes in this kind of modules way more often than React for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be worth to bring up in a weekly JavaScript chat. Maybe (at least in the short-term) we can have some naming convention to designate some script handles as internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also curious whether there's anything which prohibits that a script handle be named where we embed the specific version. Obviously, unlike an "internal" designation, this might bound us to some compatibility so far as always shipping every version we've ever used, but at least it allows to migrate to newer versions over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could work. We can define the versions for scripts in Core as well (argument of that function). Something in the PHP framework itself could work to register multiple versions and enqueue specific versions as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember now one of the issues we had considered with this is: When multiple versions are loaded in the same page, how do we handle globals being assigned without one version clobbering the other? I recall having some thoughts on using closure scoping and/or accessor properties to let the global vary depending on the consumer, but I hadn't ever formalized a proposal.