UNPKG

framer-motion

Version:

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

189 lines (164 loc) • 5.88 kB
import { visualElement } from ".." import { isForcedMotionValue } from "../../motion/utils/is-forced-motion-value" import { isMotionValue } from "../../value/utils/is-motion-value" import { VisualElementConfig } from "../types" import { checkTargetForNewValues, getOrigin } from "../utils/setters" import { getBoundingBox } from "./projection/measure" import { DOMVisualElementOptions, HTMLMutableState } from "./types" import { buildHTMLStyles } from "./utils/build-html-styles" import { isCSSVariable } from "./utils/is-css-variable" import { parseDomVariant } from "./utils/parse-dom-variant" import { isTransformProp } from "./utils/transform" import { getDefaultValueType } from "./utils/value-types" export function getComputedStyle(element: HTMLElement) { return window.getComputedStyle(element) } export const htmlConfig: VisualElementConfig< HTMLElement, HTMLMutableState, DOMVisualElementOptions > = { treeType: "dom", readValueFromInstance(domElement, key) { if (isTransformProp(key)) { const defaultType = getDefaultValueType(key) return defaultType ? defaultType.default || 0 : 0 } else { const computedStyle = getComputedStyle(domElement) return ( (isCSSVariable(key) ? computedStyle.getPropertyValue(key) : computedStyle[key]) || 0 ) } }, createRenderState: () => ({ style: {}, transform: {}, transformKeys: [], transformOrigin: {}, vars: {}, }), sortNodePosition(a, b) { /** * compareDocumentPosition returns a bitmask, by using the bitwise & * we're returning true if 2 in that bitmask is set to true. 2 is set * to true if b preceeds a. */ return a.compareDocumentPosition(b) & 2 ? 1 : -1 }, getBaseTarget(props, key) { return props.style?.[key] }, measureViewportBox(element, { transformPagePoint }) { return getBoundingBox(element, transformPagePoint) }, /** * Reset the transform on the current Element. This is called as part * of a batched process across the entire layout tree. To remove this write * cycle it'd be interesting to see if it's possible to "undo" all the current * layout transforms up the tree in the same way this.getBoundingBoxWithoutTransforms * works */ resetTransform(element, domElement, props) { /** * When we reset the transform of an element, there's a fair possibility that * the element will visually move from underneath the pointer, triggering attached * pointerenter/leave events. We temporarily suspend these while measurement takes place. */ element.suspendHoverEvents() const { transformTemplate } = props domElement.style.transform = transformTemplate ? transformTemplate({}, "") : "none" // Ensure that whatever happens next, we restore our transform on the next frame element.scheduleRender() }, restoreTransform(instance, mutableState) { instance.style.transform = mutableState.style.transform as string }, removeValueFromMutableState(key, { vars, style }) { delete vars[key] delete style[key] }, /** * Ensure that HTML and Framer-specific value types like `px`->`%` and `Color` * can be animated by Motion. */ makeTargetAnimatable( element, { transition, transitionEnd, ...target }, { transformValues }, isMounted = true ) { let origin = getOrigin(target as any, transition || {}, element) /** * If Framer has provided a function to convert `Color` etc value types, convert them */ if (transformValues) { if (transitionEnd) transitionEnd = transformValues(transitionEnd as any) if (target) target = transformValues(target as any) if (origin) origin = transformValues(origin as any) } if (isMounted) { checkTargetForNewValues(element, target, origin as any) const parsed = parseDomVariant( element, target, origin, transitionEnd ) transitionEnd = parsed.transitionEnd target = parsed.target } return { transition, transitionEnd, ...target, } }, scrapeMotionValuesFromProps(props) { const { style } = props const newValues = {} for (const key in style) { if (isMotionValue(style[key]) || isForcedMotionValue(key, props)) { newValues[key] = style[key] } } return newValues }, build( element, renderState, latestValues, projection, layoutState, options, props ) { if (element.isVisible !== undefined) { renderState.style.visibility = element.isVisible ? "visible" : "hidden" } buildHTMLStyles( renderState, latestValues, projection, layoutState, options, props.transformTemplate ) }, render(element, { style, vars }) { // Directly assign style into the Element's style prop. In tests Object.assign is the // fastest way to assign styles. Object.assign(element.style, style) // Loop over any CSS variables and assign those. for (const key in vars) { element.style.setProperty(key, vars[key] as string) } }, } export const htmlVisualElement = visualElement(htmlConfig)