framer-motion
Version:
An open source and production-ready motion library for React on the web
241 lines (213 loc) • 6.16 kB
text/typescript
import { Axis, AxisBox2D, BoxDelta, Point2D } from "../../types/geometry"
import { mix } from "popmotion"
import { ResolvedValues, VisualElement } from "../../render/types"
/**
* Reset an axis to the provided origin box.
*
* This is a mutative operation.
*/
export function resetAxis(axis: Axis, originAxis: Axis) {
axis.min = originAxis.min
axis.max = originAxis.max
}
/**
* Reset a box to the provided origin box.
*
* This is a mutative operation.
*/
export function resetBox(box: AxisBox2D, originBox: AxisBox2D) {
resetAxis(box.x, originBox.x)
resetAxis(box.y, originBox.y)
}
/**
* Scales a point based on a factor and an originPoint
*/
export function scalePoint(point: number, scale: number, originPoint: number) {
const distanceFromOrigin = point - originPoint
const scaled = scale * distanceFromOrigin
return originPoint + scaled
}
/**
* Applies a translate/scale delta to a point
*/
export function applyPointDelta(
point: number,
translate: number,
scale: number,
originPoint: number,
boxScale?: number
): number {
if (boxScale !== undefined) {
point = scalePoint(point, boxScale, originPoint)
}
return scalePoint(point, scale, originPoint) + translate
}
/**
* Applies a translate/scale delta to an axis
*/
export function applyAxisDelta(
axis: Axis,
translate: number = 0,
scale: number = 1,
originPoint: number,
boxScale?: number
): void {
axis.min = applyPointDelta(
axis.min,
translate,
scale,
originPoint,
boxScale
)
axis.max = applyPointDelta(
axis.max,
translate,
scale,
originPoint,
boxScale
)
}
/**
* Applies a translate/scale delta to a box
*/
export function applyBoxDelta(box: AxisBox2D, { x, y }: BoxDelta): void {
applyAxisDelta(box.x, x.translate, x.scale, x.originPoint)
applyAxisDelta(box.y, y.translate, y.scale, y.originPoint)
}
/**
* Apply a transform to an axis from the latest resolved motion values.
* This function basically acts as a bridge between a flat motion value map
* and applyAxisDelta
*/
export function applyAxisTransforms(
final: Axis,
axis: Axis,
transforms: ResolvedValues,
[key, scaleKey, originKey]: string[]
): void {
// Copy the current axis to the final axis before mutation
final.min = axis.min
final.max = axis.max
const axisOrigin =
transforms[originKey] !== undefined ? transforms[originKey] : 0.5
const originPoint = mix(axis.min, axis.max, axisOrigin as number)
// Apply the axis delta to the final axis
applyAxisDelta(
final,
transforms[key] as number,
transforms[scaleKey] as number,
originPoint,
transforms.scale as number
)
}
/**
* The names of the motion values we want to apply as translation, scale and origin.
*/
const xKeys = ["x", "scaleX", "originX"]
const yKeys = ["y", "scaleY", "originY"]
/**
* Apply a transform to a box from the latest resolved motion values.
*/
export function applyBoxTransforms(
finalBox: AxisBox2D,
box: AxisBox2D,
transforms: ResolvedValues
): void {
applyAxisTransforms(finalBox.x, box.x, transforms, xKeys)
applyAxisTransforms(finalBox.y, box.y, transforms, yKeys)
}
/**
* Remove a delta from a point. This is essentially the steps of applyPointDelta in reverse
*/
export function removePointDelta(
point: number,
translate: number,
scale: number,
originPoint: number,
boxScale?: number
): number {
point -= translate
point = scalePoint(point, 1 / scale, originPoint)
if (boxScale !== undefined) {
point = scalePoint(point, 1 / boxScale, originPoint)
}
return point
}
/**
* Remove a delta from an axis. This is essentially the steps of applyAxisDelta in reverse
*/
export function removeAxisDelta(
axis: Axis,
translate: number = 0,
scale: number = 1,
origin: number = 0.5,
boxScale?: number
): void {
const originPoint = mix(axis.min, axis.max, origin) - translate
axis.min = removePointDelta(
axis.min,
translate,
scale,
originPoint,
boxScale
)
axis.max = removePointDelta(
axis.max,
translate,
scale,
originPoint,
boxScale
)
}
/**
* Remove a transforms from an axis. This is essentially the steps of applyAxisTransforms in reverse
* and acts as a bridge between motion values and removeAxisDelta
*/
export function removeAxisTransforms(
axis: Axis,
transforms: ResolvedValues,
[key, scaleKey, originKey]: string[]
) {
removeAxisDelta(
axis,
transforms[key] as number,
transforms[scaleKey] as number,
transforms[originKey] as number,
transforms.scale as number
)
}
/**
* Remove a transforms from an box. This is essentially the steps of applyAxisBox in reverse
* and acts as a bridge between motion values and removeAxisDelta
*/
export function removeBoxTransforms(
box: AxisBox2D,
transforms: ResolvedValues
): void {
removeAxisTransforms(box.x, transforms, xKeys)
removeAxisTransforms(box.y, transforms, yKeys)
}
/**
* Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
*
* This is the final nested loop within updateLayoutDelta for future refactoring
*/
export function applyTreeDeltas(
box: AxisBox2D,
treeScale: Point2D,
treePath: VisualElement[]
) {
const treeLength = treePath.length
if (!treeLength) return
// Reset the treeScale
treeScale.x = treeScale.y = 1
for (let i = 0; i < treeLength; i++) {
const { delta } = treePath[i].getLayoutState()
// Incoporate each ancestor's scale into a culmulative treeScale for this component
treeScale.x *= delta.x.scale
treeScale.y *= delta.y.scale
// Apply each ancestor's calculated delta into this component's recorded layout box
applyBoxDelta(box, delta)
}
}