UNPKG

framer-motion

Version:

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

104 lines (86 loc) • 3.38 kB
import { Target, TargetWithKeyframes } from "../../../types" import { invariant } from "hey-listen" import { VisualElement } from "../../types" function isCSSVariable(value: any): value is string { return typeof value === "string" && value.startsWith("var(--") } /** * Parse Framer's special CSS variable format into a CSS token and a fallback. * * ``` * `var(--foo, #fff)` => [`--foo`, '#fff'] * ``` * * @param current */ export const cssVariableRegex = /var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/ export function parseCSSVariable(current: string) { const match = cssVariableRegex.exec(current) if (!match) return [,] const [, token, fallback] = match return [token, fallback] } const maxDepth = 4 function getVariableValue( current: string, element: HTMLElement, depth = 1 ): string | undefined { invariant( depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.` ) const [token, fallback] = parseCSSVariable(current) // No CSS variable detected if (!token) return // Attempt to read this CSS variable off the element const resolved = window.getComputedStyle(element).getPropertyValue(token) if (resolved) { return resolved.trim() } else if (isCSSVariable(fallback)) { // The fallback might itself be a CSS variable, in which case we attempt to resolve it too. return getVariableValue(fallback, element, depth + 1) } else { return fallback } } /** * Resolve CSS variables from * * @internal */ export function resolveCSSVariables( visualElement: VisualElement, { ...target }: TargetWithKeyframes, transitionEnd: Target | undefined ): { target: TargetWithKeyframes; transitionEnd?: Target } { const element = visualElement.getInstance() if (!(element instanceof HTMLElement)) return { target, transitionEnd } // If `transitionEnd` isn't `undefined`, clone it. We could clone `target` and `transitionEnd` // only if they change but I think this reads clearer and this isn't a performance-critical path. if (transitionEnd) { transitionEnd = { ...transitionEnd } } // Go through existing `MotionValue`s and ensure any existing CSS variables are resolved visualElement.forEachValue((value) => { const current = value.get() if (!isCSSVariable(current)) return const resolved = getVariableValue(current, element) if (resolved) value.set(resolved) }) // Cycle through every target property and resolve CSS variables. Currently // we only read single-var properties like `var(--foo)`, not `calc(var(--foo) + 20px)` for (const key in target) { const current = target[key] if (!isCSSVariable(current)) continue const resolved = getVariableValue(current, element) if (!resolved) continue // Clone target if it hasn't already been target[key] = resolved // If the user hasn't already set this key on `transitionEnd`, set it to the unresolved // CSS variable. This will ensure that after the animation the component will reflect // changes in the value of the CSS variable. if (transitionEnd) transitionEnd[key] ??= current } return { target, transitionEnd } }