diff --git a/packages/core/src/Motion/__snapshots__/test.tsx.snap b/packages/core/src/Motion/__snapshots__/test.tsx.snap index c73d2ff..6d9c38f 100644 --- a/packages/core/src/Motion/__snapshots__/test.tsx.snap +++ b/packages/core/src/Motion/__snapshots__/test.tsx.snap @@ -9,7 +9,7 @@ You're switching between persisted and unpersisted, don't do this. Either always exports[` self targetted motions should throw when changing into "triggerSelfKey" after initial mount 1`] = ` "@element-motion/core v0.0.0 -You're switching between self triggering modes, don't do this. Either always set the \\"triggerSelfKey\\" prop, or keep as undefined." +You're switching between self triggering modes, don't do this. Either always set the \\"triggerSelfKey\\" prop or keep as undefined." `; exports[` self targetted motions should throw when using both "in" and "triggerSelfKey" props after initial mount 1`] = ` @@ -22,7 +22,7 @@ exports[` should pass dom data to child motion 1`] = ` Array [ Object { "destination": Object { - "element": undefined, + "element":
, "elementBoundingBox": Object { "location": Object { "left": 0, @@ -40,7 +40,7 @@ Array [ }, "focalTargetElement": undefined, "focalTargetElementBoundingBox": undefined, - "render": undefined, + "render": [Function], }, "origin": Object { "element":
, @@ -71,7 +71,7 @@ exports[` should pass dom data to child motion when using in prop 1`] Array [ Object { "destination": Object { - "element": undefined, + "element":
, "elementBoundingBox": Object { "location": Object { "left": 0, @@ -89,7 +89,7 @@ Array [ }, "focalTargetElement": undefined, "focalTargetElementBoundingBox": undefined, - "render": undefined, + "render": [Function], }, "origin": Object { "element":
, diff --git a/packages/core/src/Motion/index.tsx b/packages/core/src/Motion/index.tsx index 8322146..b68fd1a 100644 --- a/packages/core/src/Motion/index.tsx +++ b/packages/core/src/Motion/index.tsx @@ -11,7 +11,7 @@ import Collector, { MotionData, MotionCallback, } from '../Collector'; -import { getElementBoundingBox } from '../lib/dom'; +import { getElementBoundingBox, eventListener } from '../lib/dom'; import defer from '../lib/defer'; import noop from '../lib/noop'; import { throwIf, warn } from '../lib/log'; @@ -57,9 +57,17 @@ export default class Motion extends React.PureComponent { + remove(); + this.execute(); + }); + } else { + this.execute(); + } + return; } @@ -111,7 +119,7 @@ export default class Motion extends React.PureComponent { - this.cancel(); - this.execute(DOMSnapshot); - }); + this.cancel(); + this.execute(DOMSnapshot); } } @@ -190,8 +194,8 @@ export default class Motion extends React.PureComponent { if (targetData.action !== CollectorActions.motion) { @@ -260,19 +272,15 @@ If it's an image, try and have the image loaded before mounting, or set a static container.insertBefore(elementToMountChildren, container.firstChild); } - // This ensures that if there was an update to the jsx that is animating it changes next frame. - // Resulting in the transition _actually_ happening. - requestAnimationFrame(() => { - if (elementToMountChildren) { - this.setState(prevState => { - const markup = prevState.motionsMarkup.concat(); - markup[index] = createPortal(jsx, elementToMountChildren!); - return { - motionsMarkup: markup, - }; - }); - } - }); + if (elementToMountChildren) { + this.setState(prevState => { + const markup = prevState.motionsMarkup.concat(); + markup[index] = createPortal(jsx, elementToMountChildren!); + return { + motionsMarkup: markup, + }; + }); + } }; const setChildProps = (props: TargetPropsFunc | null) => { diff --git a/packages/core/src/Motion/test.tsx b/packages/core/src/Motion/test.tsx index 1bd8941..1f62c8c 100644 --- a/packages/core/src/Motion/test.tsx +++ b/packages/core/src/Motion/test.tsx @@ -81,7 +81,7 @@ describe('', () => { } to={ -
+ {motion =>
} } start={false} @@ -108,7 +108,7 @@ describe('', () => { } to={ -
+ {motion =>
} } start={false} @@ -138,7 +138,7 @@ describe('', () => { )} to={ -
+ {motion =>
} } start={false} diff --git a/packages/core/src/__docs__/2-getting-started/docs.mdx b/packages/core/src/__docs__/2-getting-started/docs.mdx index 8a92e98..03fe6f4 100644 --- a/packages/core/src/__docs__/2-getting-started/docs.mdx +++ b/packages/core/src/__docs__/2-getting-started/docs.mdx @@ -319,7 +319,7 @@ and `false` when you consider it to be hidden. - + {({ ref, style }) => ( - + {({ ref, style }) => } diff --git a/packages/core/src/lib/dom.tsx b/packages/core/src/lib/dom.tsx index 1dbdc0c..96bead5 100644 --- a/packages/core/src/lib/dom.tsx +++ b/packages/core/src/lib/dom.tsx @@ -1,3 +1,13 @@ +export function eventListener( + element: HTMLElement, + event: K, + cb: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | AddEventListenerOptions | undefined +) { + element.addEventListener(event, cb, options); + return () => element.removeEventListener(event, cb, options); +} + export function getDocumentScroll() { const scrollTop = document.documentElement && document.documentElement.scrollTop diff --git a/packages/core/src/motions/FadeMove/index.tsx b/packages/core/src/motions/FadeMove/index.tsx index f3cc8d9..cc71dcf 100644 --- a/packages/core/src/motions/FadeMove/index.tsx +++ b/packages/core/src/motions/FadeMove/index.tsx @@ -73,7 +73,7 @@ export default class FadeMove extends React.Component { transformOrigin: '0 0', transform: 'translate3d(0, 0, 0) scale3d(1, 1, 1)', opacity: 1, - // Elminate any margins so they don't affect the transition. + // Eliminate any margins so they don't affect the transition. margin: 0, height: `${originTarget.size.height}px`, width: `${originTarget.size.width}px`, @@ -87,14 +87,7 @@ export default class FadeMove extends React.Component { }); }; - beforeAnimate: MotionCallback = (data, onFinish, setChildProps) => { - setChildProps({ - style: prevStyle => ({ - ...prevStyle, - opacity: 0, - }), - }); - + beforeAnimate: MotionCallback = (data, onFinish) => { onFinish(); return this.renderMotion(data); @@ -105,17 +98,6 @@ export default class FadeMove extends React.Component { return this.renderMotion(data, { moveToTarget: true }); }; - afterAnimate: MotionCallback = (_, onFinish, setChildProps) => { - setChildProps({ - style: prevStyle => ({ - ...prevStyle, - opacity: 1, - }), - }); - - onFinish(); - }; - render() { const { children } = this.props; @@ -126,7 +108,6 @@ export default class FadeMove extends React.Component { payload: { beforeAnimate: this.beforeAnimate, animate: this.animate, - afterAnimate: this.afterAnimate, }, }} > diff --git a/packages/core/src/motions/FocalRevealMove/__docs__/docs.mdx b/packages/core/src/motions/FocalRevealMove/__docs__/docs.mdx index 056559a..483ae18 100644 --- a/packages/core/src/motions/FocalRevealMove/__docs__/docs.mdx +++ b/packages/core/src/motions/FocalRevealMove/__docs__/docs.mdx @@ -196,7 +196,7 @@ import Motion, { FocalRevealMove, FocalTarget } from '@element-motion/core'; -## Caveats +## Gotchas Reveal works by default modifying the width and height of the element, starting from the [FocalTarget](/focal-target) element. diff --git a/packages/core/src/motions/Move/__docs__/docs.mdx b/packages/core/src/motions/Move/__docs__/docs.mdx index 1ad4f7e..019c2fe 100644 --- a/packages/core/src/motions/Move/__docs__/docs.mdx +++ b/packages/core/src/motions/Move/__docs__/docs.mdx @@ -47,3 +47,10 @@ import { Move } from '@element-motion/core'; ## Props + +## Gotchas + +Noticing that your in transit element isn't being stacked correctly? +You'll want to create a [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context) by opting into `createStackingContext` which will set `position: relative` to the inflight element. + +This should fix your stacking problems. diff --git a/packages/core/src/motions/Move/index.tsx b/packages/core/src/motions/Move/index.tsx index 2f168bf..639fe8f 100644 --- a/packages/core/src/motions/Move/index.tsx +++ b/packages/core/src/motions/Move/index.tsx @@ -57,6 +57,13 @@ export interface MoveProps extends CollectorChildrenProps { * Defaults to true. */ scaleY?: boolean; + + /** + * Will set "position: relative" on the element during a transition. + * Useful for creating a stacking context to position the element where you want in the stack. + * Use "zIndex" prop to set the appropriate position in the stack. + */ + createStackingContext?: boolean; } export default class Move extends React.Component { @@ -74,7 +81,15 @@ export default class Move extends React.Component { abort = noop; beforeAnimate: MotionCallback = (data, onFinish, setChildProps) => { - const { zIndex, useFocalTarget, transformX, transformY, scaleX, scaleY } = this.props; + const { + zIndex, + useFocalTarget, + transformX, + transformY, + scaleX, + scaleY, + createStackingContext, + } = this.props; if (process.env.NODE_ENV === 'development') { throwIf( @@ -102,7 +117,7 @@ export default class Move extends React.Component { style: prevStyles => ({ ...prevStyles, zIndex, - opacity: 1, + position: createStackingContext ? 'relative' : undefined, transformOrigin: '0 0', visibility: 'visible', willChange: combine('transform')(prevStyles.willChange), diff --git a/packages/core/src/motions/ReshapingContainer/index.tsx b/packages/core/src/motions/ReshapingContainer/index.tsx index 732d4dc..bc20093 100644 --- a/packages/core/src/motions/ReshapingContainer/index.tsx +++ b/packages/core/src/motions/ReshapingContainer/index.tsx @@ -165,7 +165,14 @@ export default class ReshapingContainer extends React.PureComponent {/* Position relative/zIndex needed to position this above the floating background. */} - {children({ style: { position: 'relative', zIndex: 2, maxHeight } })} + {children({ + style: { + maxHeight, + zIndex: 2, + // Using position: relative fucks out in Safari with clip-path resulting in clip-path not transitioning + position: 'relative', + }, + })} )} diff --git a/packages/core/src/motions/Reveal/__docz__/docs.mdx b/packages/core/src/motions/Reveal/__docz__/docs.mdx index c92a4a9..50e1713 100644 --- a/packages/core/src/motions/Reveal/__docz__/docs.mdx +++ b/packages/core/src/motions/Reveal/__docz__/docs.mdx @@ -22,9 +22,16 @@ import { Reveal } from '@element-motion/core'; -## Caveats +## Gotchas + +### Collapsing margins Be careful of collapsing margins when utilising this motion, they will make the destination element jump around, probably. If you're seeing some odd behavior - maybe try a flex container instead. + +### Composing with move + +When composing with any motion that uses `transform` or `position: relative`, +Reveal will not work in Safari - follow the bug here: https://bugs.webkit.org/show_bug.cgi?id=196731. diff --git a/packages/core/src/motions/Reveal/index.tsx b/packages/core/src/motions/Reveal/index.tsx index 03e2d9c..c2734f0 100644 --- a/packages/core/src/motions/Reveal/index.tsx +++ b/packages/core/src/motions/Reveal/index.tsx @@ -57,10 +57,9 @@ export default class Reveal extends React.Component { data.origin.elementBoundingBox ? { ...prevStyles, + WebkitClipPath: `inset(${topOffset}px ${right}px ${bottom}px ${leftOffset}px)`, clipPath: `inset(${topOffset}px ${right}px ${bottom}px ${leftOffset}px)`, - opacity: 1, - visibility: 'visible', - willChange: combine('clip-path')(prevStyles.willChange), + willChange: combine('clip-path, -webkit-clip-path')(prevStyles.willChange), } : undefined, }); @@ -78,10 +77,11 @@ export default class Reveal extends React.Component { setChildProps({ style: prevStyles => ({ ...prevStyles, + WebkitClipPath: 'inset(0px)', clipPath: 'inset(0px)', - transition: combine(`clip-path ${calculatedDuration}ms ${timingFunction}`)( - prevStyles.transition - ), + transition: combine( + `-webkit-clip-path ${calculatedDuration}ms ${timingFunction}, clip-path ${calculatedDuration}ms ${timingFunction}` + )(prevStyles.transition), }), }); diff --git a/packages/core/src/motions/RevealReshapingContainer/__docz__/docs.mdx b/packages/core/src/motions/RevealReshapingContainer/__docz__/docs.mdx index d2a65cc..0c8b305 100644 --- a/packages/core/src/motions/RevealReshapingContainer/__docz__/docs.mdx +++ b/packages/core/src/motions/RevealReshapingContainer/__docz__/docs.mdx @@ -118,7 +118,7 @@ import { ReshapingContainer } from '@element-motion/core'; -## Caveats +## Gotchas Be careful of collapsing margins when utilising this motion, they will make the destination element jump around when revealing, diff --git a/packages/core/src/motions/RevealReshapingContainer/index.tsx b/packages/core/src/motions/RevealReshapingContainer/index.tsx index 8130ab2..4402a3c 100644 --- a/packages/core/src/motions/RevealReshapingContainer/index.tsx +++ b/packages/core/src/motions/RevealReshapingContainer/index.tsx @@ -66,6 +66,7 @@ export default class RevealReshapingContainer extends React.PureComponent< render() { const { children, duration, timingFunction, triggerKey } = this.props; + // The Move transition using transform fucks out in Safari with clip-path resulting in clip-path not transitioning return ( {reshaping => (