This repository has been archived by the owner on Apr 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(translate): adds new translate motion
- Loading branch information
Showing
9 changed files
with
362 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as React from 'react'; | ||
import { INVERSE_TRANSLATE_CLASS_NAME } from './index'; | ||
|
||
interface InverseTranslateProps { | ||
children: (opts: { className: string }) => React.ReactElement; | ||
} | ||
|
||
const InverseTranslate: React.FC<InverseTranslateProps> = (props: InverseTranslateProps) => | ||
props.children({ className: INVERSE_TRANSLATE_CLASS_NAME }); | ||
|
||
export default InverseTranslate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
name: Translate | ||
route: /translate | ||
menu: Focal motions | ||
--- | ||
|
||
import { Playground, Props } from 'docz'; | ||
import { Toggler } from '@element-motion/dev'; | ||
import { Motion } from '@element-motion/utils'; | ||
import Translate from '../index'; | ||
import InverseTranslate from '../InverseTranslate'; | ||
import { Menu } from './styled'; | ||
|
||
# Translate | ||
|
||
Will translate an element from the `origin` to `destination` location. | ||
Can also use `InverseTranslate` component to counteract the transform. | ||
|
||
## Usage | ||
|
||
```js | ||
import Motion, { Translate, InverseTranslate } from '@element-motion/core'; | ||
``` | ||
|
||
**Try the interactive demos** 👇 | ||
|
||
### Without inverse translate | ||
|
||
<Playground> | ||
<Toggler> | ||
{toggler => ( | ||
<Motion triggerSelfKey={toggler.shown}> | ||
<Translate> | ||
{motion => ( | ||
<Menu {...motion} right={toggler.shown} onClick={toggler.toggle}> | ||
<div>hello, world</div> | ||
</Menu> | ||
)} | ||
</Translate> | ||
</Motion> | ||
)} | ||
</Toggler> | ||
</Playground> | ||
|
||
### With inverse translate | ||
|
||
<Playground> | ||
<Toggler> | ||
{toggler => ( | ||
<Motion triggerSelfKey={toggler.shown}> | ||
<Translate> | ||
{motion => ( | ||
<Menu {...motion} right={toggler.shown} onClick={toggler.toggle}> | ||
<InverseTranslate>{inverse => <div {...inverse}>hello, world</div>}</InverseTranslate> | ||
</Menu> | ||
)} | ||
</Translate> | ||
</Motion> | ||
)} | ||
</Toggler> | ||
</Playground> | ||
|
||
## Props | ||
|
||
<Props of={Translate} /> | ||
|
||
## Gotchas | ||
|
||
### Inverse translate | ||
|
||
Make sure to utilise an element that is a block - either `block`, `inline-block`, `flex`, `inline-flex`. | ||
Else the transforms will not be applied. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import styled from 'styled-components'; | ||
import { colors } from '@element-motion/dev'; | ||
|
||
export const Menu = styled.div<any>` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
background-color: ${colors.red}; | ||
border-radius: ${props => (props.isExpanded ? 3 : 1)}px; | ||
color: white; | ||
height: 200px; | ||
width: 200px; | ||
margin-left: ${props => (props.right ? 'auto' : 0)}; | ||
cursor: pointer; | ||
> div { | ||
background-color: black; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import * as React from 'react'; | ||
import { css, keyframes, cx } from 'emotion'; | ||
import { | ||
Collector, | ||
CollectorChildrenProps, | ||
CollectorActions, | ||
MotionData, | ||
bezierToFunc, | ||
combine, | ||
standard, | ||
dynamic, | ||
recalculateElementBoundingBoxFromScroll, | ||
} from '@element-motion/utils'; | ||
import { MotionProps } from '../types'; | ||
|
||
export interface TranslateProps extends CollectorChildrenProps, MotionProps {} | ||
|
||
const buildKeyframes = (elements: MotionData, duration: number, bezierCurve: string) => { | ||
const frameTime = 1000 / 60; | ||
const nFrames = Math.round(duration / frameTime); | ||
const percentIncrement = 100 / nFrames; | ||
const originBoundingBox = recalculateElementBoundingBoxFromScroll( | ||
elements.origin.elementBoundingBox | ||
); | ||
const destinationBoundingBox = elements.destination.elementBoundingBox; | ||
const timingFunction = bezierToFunc(bezierCurve, duration); | ||
|
||
const startX = originBoundingBox.location.left - destinationBoundingBox.location.left; | ||
const startY = originBoundingBox.location.top - destinationBoundingBox.location.top; | ||
const endX = 0; | ||
const endY = 0; | ||
const animation: string[] = []; | ||
const inverseAnimation: string[] = []; | ||
|
||
for (let i = 0; i <= nFrames; i += 1) { | ||
const step = Number(timingFunction(i / nFrames).toFixed(5)); | ||
const percentage = Number((i * percentIncrement).toFixed(5)); | ||
const translateToX = Number((startX + (endX - startX) * step).toFixed(5)); | ||
const translateToY = Number((startY + (endY - startY) * step).toFixed(5)); | ||
const inverseTranslateToX = -translateToX; | ||
const inverseTranslateToY = -translateToY; | ||
|
||
animation.push(` | ||
${percentage}% { | ||
transform: translate3d(${translateToX}px, ${translateToY}px, 0); | ||
}`); | ||
|
||
inverseAnimation.push(` | ||
${percentage}% { | ||
transform: translate3d(${inverseTranslateToX}px, ${inverseTranslateToY}px, 0); | ||
}`); | ||
} | ||
|
||
return { | ||
animation: keyframes(animation), | ||
inverseAnimation: keyframes(inverseAnimation), | ||
}; | ||
}; | ||
|
||
export const INVERSE_TRANSLATE_CLASS_NAME = 'inVRsE-tRnsFrm'; | ||
|
||
const Translate: React.FC<TranslateProps> = ({ | ||
children, | ||
duration = 'dynamic', | ||
timingFunction = standard(), | ||
}: TranslateProps) => { | ||
let calculatedDuration: number; | ||
let animation: string; | ||
let inverseAnimation: string; | ||
|
||
return ( | ||
<Collector | ||
data={{ | ||
action: CollectorActions.motion, | ||
payload: { | ||
beforeAnimate: (elements, onFinish, setChildProps) => { | ||
const originBoundingBox = recalculateElementBoundingBoxFromScroll( | ||
elements.origin.elementBoundingBox | ||
); | ||
const destinationBoundingBox = elements.destination.elementBoundingBox; | ||
const translateToX = | ||
originBoundingBox.location.left - destinationBoundingBox.location.left; | ||
const translateToY = | ||
originBoundingBox.location.top - destinationBoundingBox.location.top; | ||
const inverseTranslateToX = -translateToX; | ||
const inverseTranslateToY = -translateToY; | ||
|
||
calculatedDuration = | ||
duration === 'dynamic' | ||
? dynamic( | ||
elements.origin.elementBoundingBox, | ||
elements.destination.elementBoundingBox | ||
) | ||
: duration; | ||
|
||
({ animation, inverseAnimation } = buildKeyframes( | ||
elements, | ||
calculatedDuration, | ||
timingFunction | ||
)); | ||
|
||
const common = { | ||
willChange: 'transform', | ||
transformOrigin: 'top left', | ||
animationTimingFunction: 'step-end', | ||
animationFillMode: 'forwards', | ||
animationPlayState: 'paused', | ||
}; | ||
|
||
setChildProps({ | ||
style: prevStyle => ({ | ||
...prevStyle, | ||
...common, | ||
transform: combine(`translate3d(${translateToX}px, ${translateToY}px, 0)`, '')( | ||
prevStyle.transform | ||
), | ||
animationDuration: `${calculatedDuration}ms`, | ||
animationName: combine(animation)(prevStyle.animationName), | ||
}), | ||
className: () => | ||
css({ | ||
[`.${INVERSE_TRANSLATE_CLASS_NAME}`]: { | ||
...common, | ||
transform: `translate3d(${inverseTranslateToX}px, ${inverseTranslateToY}px, 0)`, | ||
animationDuration: `${calculatedDuration}ms`, | ||
animationName: inverseAnimation, | ||
}, | ||
}), | ||
}); | ||
|
||
onFinish(); | ||
}, | ||
animate: (_, onFinish, setChildProps) => { | ||
setChildProps({ | ||
style: prevStyle => ({ | ||
...prevStyle, | ||
animationPlayState: 'running', | ||
}), | ||
className: prevClassName => | ||
cx( | ||
prevClassName, | ||
css({ | ||
[`.${INVERSE_TRANSLATE_CLASS_NAME}`]: { | ||
animationPlayState: 'running', | ||
}, | ||
}) | ||
), | ||
}); | ||
|
||
setTimeout(onFinish, calculatedDuration); | ||
}, | ||
}, | ||
}} | ||
> | ||
{children} | ||
</Collector> | ||
); | ||
}; | ||
|
||
export default Translate; |
Oops, something went wrong.