framer-motion
Version:
An open source and production-ready motion library for React on the web
82 lines (70 loc) • 2.68 kB
text/typescript
import { Axis, AxisDelta, BoxDelta, AxisBox2D } from "../../types/geometry"
import { mix, distance, progress, clamp } from "popmotion"
import { ResolvedValues } from "../../render/types"
const clampProgress = (v: number) => clamp(0, 1, v)
/**
* Returns true if the provided value is within maxDistance of the provided target
*/
export function isNear(value: number, target = 0, maxDistance = 0.01): boolean {
return distance(value, target) < maxDistance
}
function calcLength(axis: Axis) {
return axis.max - axis.min
}
/**
* Calculate a transform origin relative to the source axis, between 0-1, that results
* in an asthetically pleasing scale/transform needed to project from source to target.
*/
export function calcOrigin(source: Axis, target: Axis): number {
let origin = 0.5
const sourceLength = calcLength(source)
const targetLength = calcLength(target)
if (targetLength > sourceLength) {
origin = progress(target.min, target.max - sourceLength, source.min)
} else if (sourceLength > targetLength) {
origin = progress(source.min, source.max - targetLength, target.min)
}
return clampProgress(origin)
}
/**
* Update the AxisDelta with a transform that projects source into target.
*
* The transform `origin` is optional. If not provided, it'll be automatically
* calculated based on the relative positions of the two bounding boxes.
*/
export function updateAxisDelta(
delta: AxisDelta,
source: Axis,
target: Axis,
origin: number = 0.5
) {
delta.origin = origin
delta.originPoint = mix(source.min, source.max, delta.origin)
delta.scale = calcLength(target) / calcLength(source)
if (isNear(delta.scale, 1, 0.0001)) delta.scale = 1
delta.translate =
mix(target.min, target.max, delta.origin) - delta.originPoint
if (isNear(delta.translate)) delta.translate = 0
}
/**
* Update the BoxDelta with a transform that projects the source into the target.
*
* The transform `origin` is optional. If not provided, it'll be automatically
* calculated based on the relative positions of the two bounding boxes.
*/
export function updateBoxDelta(
delta: BoxDelta,
source: AxisBox2D,
target: AxisBox2D,
origin: ResolvedValues
): void {
updateAxisDelta(delta.x, source.x, target.x, defaultOrigin(origin.originX))
updateAxisDelta(delta.y, source.y, target.y, defaultOrigin(origin.originY))
}
/**
* Currently this only accepts numerical origins, measured as 0-1, but could
* accept pixel values by comparing to the target axis.
*/
function defaultOrigin(origin: number | string | null) {
return typeof origin === "number" ? origin : 0.5
}