UNPKG

framer-motion

Version:

An open source and production-ready motion library for React on the web

131 lines (113 loc) • 4.28 kB
import { sortTransformProps } from "./transform" import { ResolvedValues } from "../../types" import { DOMVisualElementOptions, HTMLMutableState, TransformOrigin, } from "../types" import { MotionProps } from "../../../motion/types" import { BoxDelta, Point2D } from "../../../types/geometry" import { LayoutState, zeroLayout } from "../../utils/state" const translateAlias: { [key: string]: string } = { x: "translateX", y: "translateY", z: "translateZ", transformPerspective: "perspective", } /** * Build a CSS transform style from individual x/y/scale etc properties. * * This outputs with a default order of transforms/scales/rotations, this can be customised by * providing a transformTemplate function. */ export function buildTransform( { transform, transformKeys }: HTMLMutableState, { enableHardwareAcceleration = true, allowTransformNone = true, }: DOMVisualElementOptions, transformIsDefault: boolean, transformTemplate?: MotionProps["transformTemplate"] ) { // The transform string we're going to build into. let transformString = "" // Transform keys into their default order - this will determine the output order. transformKeys.sort(sortTransformProps) // Track whether the defined transform has a defined z so we don't add a // second to enable hardware acceleration let transformHasZ = false // Loop over each transform and build them into transformString const numTransformKeys = transformKeys.length for (let i = 0; i < numTransformKeys; i++) { const key = transformKeys[i] transformString += `${translateAlias[key] || key}(${transform[key]}) ` if (key === "z") transformHasZ = true } if (!transformHasZ && enableHardwareAcceleration) { transformString += "translateZ(0)" } else { transformString = transformString.trim() } // If we have a custom `transform` template, pass our transform values and // generated transformString to that before returning if (transformTemplate) { transformString = transformTemplate( transform, transformIsDefault ? "" : transformString ) } else if (allowTransformNone && transformIsDefault) { transformString = "none" } return transformString } /** * Build a transformOrigin style. Uses the same defaults as the browser for * undefined origins. */ export function buildTransformOrigin({ originX = "50%", originY = "50%", originZ = 0, }: TransformOrigin) { return `${originX} ${originY} ${originZ}` } /** * Build a transform style that takes a calculated delta between the element's current * space on screen and projects it into the desired space. */ export function buildLayoutProjectionTransform( { x, y }: BoxDelta, treeScale: Point2D, latestTransform?: ResolvedValues ): string { /** * The translations we use to calculate are always relative to the viewport coordinate space. * But when we apply scales, we also scale the coordinate space of an element and its children. * For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need * to move an element 100 pixels, we actually need to move it 200 in within that scaled space. */ const xTranslate = x.translate / treeScale.x const yTranslate = y.translate / treeScale.y let transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) ` if (latestTransform) { const { rotate, rotateX, rotateY } = latestTransform if (rotate) transform += `rotate(${rotate}) ` if (rotateX) transform += `rotateX(${rotateX}) ` if (rotateY) transform += `rotateY(${rotateY}) ` } transform += `scale(${x.scale}, ${y.scale})` return !latestTransform && transform === identityProjection ? "" : transform } /** * Take the calculated delta origin and apply it as a transform string. */ export function buildLayoutProjectionTransformOrigin({ deltaFinal, }: LayoutState) { return `${deltaFinal.x.origin * 100}% ${deltaFinal.y.origin * 100}% 0` } export const identityProjection = buildLayoutProjectionTransform( zeroLayout.delta, zeroLayout.treeScale, { x: 1, y: 1 } )