Skip to content
This repository was archived by the owner on Apr 14, 2020. It is now read-only.

Commit d010313

Browse files
authored
Merge pull request #132 from element-motion/fix-reveal-in-safari
Fix motion issues in safari
2 parents 2d8aed0 + ec29c7c commit d010313

14 files changed

Lines changed: 104 additions & 68 deletions

File tree

packages/core/src/Motion/__snapshots__/test.tsx.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ You're switching between persisted and unpersisted, don't do this. Either always
99
exports[`<Motion /> self targetted motions should throw when changing into "triggerSelfKey" after initial mount 1`] = `
1010
"@element-motion/core v0.0.0
1111
12-
You're switching between self triggering modes, don't do this. Either always set the \\"triggerSelfKey\\" prop, or keep as undefined."
12+
You're switching between self triggering modes, don't do this. Either always set the \\"triggerSelfKey\\" prop or keep as undefined."
1313
`;
1414

1515
exports[`<Motion /> self targetted motions should throw when using both "in" and "triggerSelfKey" props after initial mount 1`] = `
@@ -22,7 +22,7 @@ exports[`<Motion /> should pass dom data to child motion 1`] = `
2222
Array [
2323
Object {
2424
"destination": Object {
25-
"element": undefined,
25+
"element": <div />,
2626
"elementBoundingBox": Object {
2727
"location": Object {
2828
"left": 0,
@@ -40,7 +40,7 @@ Array [
4040
},
4141
"focalTargetElement": undefined,
4242
"focalTargetElementBoundingBox": undefined,
43-
"render": undefined,
43+
"render": [Function],
4444
},
4545
"origin": Object {
4646
"element": <div />,
@@ -71,7 +71,7 @@ exports[`<Motion /> should pass dom data to child motion when using in prop 1`]
7171
Array [
7272
Object {
7373
"destination": Object {
74-
"element": undefined,
74+
"element": <div />,
7575
"elementBoundingBox": Object {
7676
"location": Object {
7777
"left": 0,
@@ -89,7 +89,7 @@ Array [
8989
},
9090
"focalTargetElement": undefined,
9191
"focalTargetElementBoundingBox": undefined,
92-
"render": undefined,
92+
"render": [Function],
9393
},
9494
"origin": Object {
9595
"element": <div />,

packages/core/src/Motion/index.tsx

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Collector, {
1111
MotionData,
1212
MotionCallback,
1313
} from '../Collector';
14-
import { getElementBoundingBox } from '../lib/dom';
14+
import { getElementBoundingBox, eventListener } from '../lib/dom';
1515
import defer from '../lib/defer';
1616
import noop from '../lib/noop';
1717
import { throwIf, warn } from '../lib/log';
@@ -57,9 +57,17 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
5757
);
5858
}
5959

60-
if (componentIn === undefined && store.has(name)) {
60+
if (this.element && componentIn === undefined && store.has(name)) {
6161
// A child has already been stored, so this is probably the matching pair.
62-
this.execute();
62+
if (this.element.tagName === 'IMG' && !(this.element as HTMLImageElement).complete) {
63+
const remove = eventListener(this.element, 'load', () => {
64+
remove();
65+
this.execute();
66+
});
67+
} else {
68+
this.execute();
69+
}
70+
6371
return;
6472
}
6573

@@ -111,7 +119,7 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
111119
throwIf(
112120
(this.props.triggerSelfKey === undefined || prevProps.triggerSelfKey === undefined) &&
113121
!triggerSelfKeyPropSame,
114-
`You're switching between self triggering modes, don't do this. Either always set the "triggerSelfKey" prop, or keep as undefined.`
122+
`You're switching between self triggering modes, don't do this. Either always set the "triggerSelfKey" prop or keep as undefined.`
115123
);
116124
}
117125

@@ -127,12 +135,8 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
127135
}
128136

129137
if (!triggerSelfKeyPropSame) {
130-
// Defer execution to the next frame to capture correctly.
131-
// Make sure to keep react state the same for any inflight motions to be captured correctly.
132-
requestAnimationFrame(() => {
133-
this.cancel();
134-
this.execute(DOMSnapshot);
135-
});
138+
this.cancel();
139+
this.execute(DOMSnapshot);
136140
}
137141
}
138142

@@ -190,8 +194,8 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
190194
: undefined;
191195

192196
if (process.env.NODE_ENV === 'development' && elementBoundingBox.size.height === 0) {
193-
warn(`Your target child had a height of zero when capturing it's DOM data. This may affect the motion.
194-
If it's an image, try and have the image loaded before mounting, or set a static height.`);
197+
warn(`Your origin element had a height of zero when capturing it's DOM data. This may affect the motion.
198+
If it's an image, try and have the image loaded before mounting or set a static height.`);
195199
}
196200

197201
const { name } = this.props;
@@ -244,6 +248,14 @@ If it's an image, try and have the image loaded before mounting, or set a static
244248
},
245249
};
246250

251+
if (
252+
process.env.NODE_ENV === 'development' &&
253+
motionData.destination.elementBoundingBox.size.height === 0
254+
) {
255+
warn(`Your destination element had a height of zero when capturing it's DOM data. This may affect the motion.
256+
If it's an image, try and have the image loaded before mounting or set a static height.`);
257+
}
258+
247259
// Loads each action up in an easy-to-execute format.
248260
const actions = collectorData.map((targetData, index) => {
249261
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
260272
container.insertBefore(elementToMountChildren, container.firstChild);
261273
}
262274

263-
// This ensures that if there was an update to the jsx that is animating it changes next frame.
264-
// Resulting in the transition _actually_ happening.
265-
requestAnimationFrame(() => {
266-
if (elementToMountChildren) {
267-
this.setState(prevState => {
268-
const markup = prevState.motionsMarkup.concat();
269-
markup[index] = createPortal(jsx, elementToMountChildren!);
270-
return {
271-
motionsMarkup: markup,
272-
};
273-
});
274-
}
275-
});
275+
if (elementToMountChildren) {
276+
this.setState(prevState => {
277+
const markup = prevState.motionsMarkup.concat();
278+
markup[index] = createPortal(jsx, elementToMountChildren!);
279+
return {
280+
motionsMarkup: markup,
281+
};
282+
});
283+
}
276284
};
277285

278286
const setChildProps = (props: TargetPropsFunc | null) => {

packages/core/src/Motion/test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('<Motion />', () => {
8181
}
8282
to={
8383
<Motion name="anim-0" onFinish={done}>
84-
<div />
84+
{motion => <div {...motion} />}
8585
</Motion>
8686
}
8787
start={false}
@@ -108,7 +108,7 @@ describe('<Motion />', () => {
108108
}
109109
to={
110110
<Motion name="anim-1" onFinish={deferred.resolve}>
111-
<div />
111+
{motion => <div {...motion} />}
112112
</Motion>
113113
}
114114
start={false}
@@ -138,7 +138,7 @@ describe('<Motion />', () => {
138138
)}
139139
to={
140140
<Motion name="anim-aa" onFinish={deferred.resolve}>
141-
<div />
141+
{motion => <div {...motion} />}
142142
</Motion>
143143
}
144144
start={false}

packages/core/src/__docs__/2-getting-started/docs.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ and `false` when you consider it to be hidden.
319319
<Styled.Root>
320320
<Styled.ImageContainer>
321321
<Motion name={src} in={inn}>
322-
<Move>
322+
<Move zIndex={89}>
323323
{({ ref, style }) => (
324324
<Styled.Img
325325
src={src}
@@ -355,7 +355,7 @@ and `false` when you consider it to be hidden.
355355
</div>
356356

357357
<Motion name={src}>
358-
<Move>
358+
<Move zIndex={89} createStackingContext>
359359
{({ ref, style }) => <Styled.PageImage src={src} ref={ref} style={style} />}
360360
</Move>
361361
</Motion>

packages/core/src/lib/dom.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
export function eventListener<K extends keyof HTMLElementEventMap>(
2+
element: HTMLElement,
3+
event: K,
4+
cb: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
5+
options?: boolean | AddEventListenerOptions | undefined
6+
) {
7+
element.addEventListener<K>(event, cb, options);
8+
return () => element.removeEventListener(event, cb, options);
9+
}
10+
111
export function getDocumentScroll() {
212
const scrollTop =
313
document.documentElement && document.documentElement.scrollTop

packages/core/src/motions/FadeMove/index.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
7373
transformOrigin: '0 0',
7474
transform: 'translate3d(0, 0, 0) scale3d(1, 1, 1)',
7575
opacity: 1,
76-
// Elminate any margins so they don't affect the transition.
76+
// Eliminate any margins so they don't affect the transition.
7777
margin: 0,
7878
height: `${originTarget.size.height}px`,
7979
width: `${originTarget.size.width}px`,
@@ -87,14 +87,7 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
8787
});
8888
};
8989

90-
beforeAnimate: MotionCallback = (data, onFinish, setChildProps) => {
91-
setChildProps({
92-
style: prevStyle => ({
93-
...prevStyle,
94-
opacity: 0,
95-
}),
96-
});
97-
90+
beforeAnimate: MotionCallback = (data, onFinish) => {
9891
onFinish();
9992

10093
return this.renderMotion(data);
@@ -105,17 +98,6 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
10598
return this.renderMotion(data, { moveToTarget: true });
10699
};
107100

108-
afterAnimate: MotionCallback = (_, onFinish, setChildProps) => {
109-
setChildProps({
110-
style: prevStyle => ({
111-
...prevStyle,
112-
opacity: 1,
113-
}),
114-
});
115-
116-
onFinish();
117-
};
118-
119101
render() {
120102
const { children } = this.props;
121103

@@ -126,7 +108,6 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
126108
payload: {
127109
beforeAnimate: this.beforeAnimate,
128110
animate: this.animate,
129-
afterAnimate: this.afterAnimate,
130111
},
131112
}}
132113
>

packages/core/src/motions/FocalRevealMove/__docs__/docs.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ import Motion, { FocalRevealMove, FocalTarget } from '@element-motion/core';
196196

197197
<Props of={FocalRevealMove} />
198198

199-
## Caveats
199+
## Gotchas
200200

201201
Reveal works by default modifying the width and height of the element,
202202
starting from the [FocalTarget](/focal-target) element.

packages/core/src/motions/Move/__docs__/docs.mdx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,10 @@ import { Move } from '@element-motion/core';
4747
## Props
4848

4949
<Props of={Move} />
50+
51+
## Gotchas
52+
53+
Noticing that your in transit element isn't being stacked correctly?
54+
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.
55+
56+
This should fix your stacking problems.

packages/core/src/motions/Move/index.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ export interface MoveProps extends CollectorChildrenProps {
5757
* Defaults to true.
5858
*/
5959
scaleY?: boolean;
60+
61+
/**
62+
* Will set "position: relative" on the element during a transition.
63+
* Useful for creating a stacking context to position the element where you want in the stack.
64+
* Use "zIndex" prop to set the appropriate position in the stack.
65+
*/
66+
createStackingContext?: boolean;
6067
}
6168

6269
export default class Move extends React.Component<MoveProps> {
@@ -74,7 +81,15 @@ export default class Move extends React.Component<MoveProps> {
7481
abort = noop;
7582

7683
beforeAnimate: MotionCallback = (data, onFinish, setChildProps) => {
77-
const { zIndex, useFocalTarget, transformX, transformY, scaleX, scaleY } = this.props;
84+
const {
85+
zIndex,
86+
useFocalTarget,
87+
transformX,
88+
transformY,
89+
scaleX,
90+
scaleY,
91+
createStackingContext,
92+
} = this.props;
7893

7994
if (process.env.NODE_ENV === 'development') {
8095
throwIf(
@@ -102,7 +117,7 @@ export default class Move extends React.Component<MoveProps> {
102117
style: prevStyles => ({
103118
...prevStyles,
104119
zIndex,
105-
opacity: 1,
120+
position: createStackingContext ? 'relative' : undefined,
106121
transformOrigin: '0 0',
107122
visibility: 'visible',
108123
willChange: combine('transform')(prevStyles.willChange),

packages/core/src/motions/ReshapingContainer/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,14 @@ export default class ReshapingContainer extends React.PureComponent<ReshapingCon
165165
/>
166166

167167
{/* Position relative/zIndex needed to position this above the floating background. */}
168-
{children({ style: { position: 'relative', zIndex: 2, maxHeight } })}
168+
{children({
169+
style: {
170+
maxHeight,
171+
zIndex: 2,
172+
// Using position: relative fucks out in Safari with clip-path resulting in clip-path not transitioning
173+
position: 'relative',
174+
},
175+
})}
169176
</ComponentAs>
170177
)}
171178
</Move>

0 commit comments

Comments
 (0)