UNPKG

tailwindcss-motion

Version:

Tailwind Motion is a Tailwind CSS Plugin made by Rombo. It’s a simple, yet powerful, animation library for Tailwind CSS.

328 lines (309 loc) 10.4 kB
import type { CSSRuleObject, PluginAPI } from "tailwindcss/types/config.js"; type SpringMultipliers = { [key: string]: string; }; type ThemeConfig = { motionDelay: Record<string, string>; motionDuration: Record<string, string>; motionLoopCount: Record<string, string>; motionTimingFunction: Record<string, string>; transitionDuration: Record<string, string>; transitionTimingFunction: Record<string, string>; }; type UtilityOptions = { modifier: string | null; }; // Define spring types and their corresponding perceptual duration multipliers export const springPerceptualMultipliers: SpringMultipliers = { "var(--motion-spring-smooth)": "1.66", "var(--motion-spring-snappy)": "1.66", "var(--motion-spring-bouncy)": "1.66", "var(--motion-spring-bouncier)": "2.035", "var(--motion-spring-bounciest)": "5.285", "var(--motion-bounce)": "2", }; export function addModifiers( matchUtilities: PluginAPI["matchUtilities"], addUtilities: PluginAPI["addUtilities"], theme: (path: keyof ThemeConfig) => Record<string, string> ) { // duration matchUtilities( { "motion-duration": (value: string, { modifier }: UtilityOptions) => { switch (modifier) { case "scale": return { "--motion-scale-duration": value } as CSSRuleObject; case "translate": return { "--motion-translate-duration": value }; case "rotate": return { "--motion-rotate-duration": value }; case "blur": case "grayscale": return { "--motion-filter-duration": value }; case "opacity": return { "--motion-opacity-duration": value }; case "background": return { "--motion-background-color-duration": value }; case "text": return { "--motion-text-color-duration": value }; default: return { "--motion-duration": value, }; } }, }, { values: theme("motionDuration"), modifiers: { scale: "scale", translate: "translate", rotate: "rotate", blur: "blur", grayscale: "grayscale", opacity: "opacity", background: "background", text: "text", }, } ); // delay matchUtilities( { "motion-delay": (value: string, { modifier }: UtilityOptions) => { switch (modifier) { case "scale": return { "--motion-scale-delay": value } as CSSRuleObject; case "translate": return { "--motion-translate-delay": value }; case "rotate": return { "--motion-rotate-delay": value }; case "blur": case "grayscale": return { "--motion-filter-delay": value }; case "opacity": return { "--motion-opacity-delay": value }; case "background": return { "--motion-background-color-delay": value }; case "text": return { "--motion-text-color-delay": value }; default: return { "--motion-delay": value, }; } }, }, { values: theme("motionDelay"), modifiers: { scale: "scale", translate: "translate", rotate: "rotate", blur: "blur", grayscale: "grayscale", opacity: "opacity", background: "background", text: "text", }, } ); // ease matchUtilities( { "motion-ease": (value: string, { modifier }: UtilityOptions) => { // if the ease isn't a spring, the multiplier doesn't change anything const perceptualDurationMultiplier = springPerceptualMultipliers[value] || 1; const isSpringWithBounce = [ "var(--motion-spring-bouncy)", "var(--motion-spring-bouncier)", "var(--motion-spring-bounciest)", "var(--motion-bounce)", ].includes(value); switch (modifier) { case "scale": return { "--motion-scale-timing": value, "--motion-scale-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, } as CSSRuleObject; case "translate": return { "--motion-translate-timing": value, "--motion-translate-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; case "rotate": return { "--motion-rotate-timing": value, "--motion-rotate-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; case "blur": case "grayscale": return { "--motion-filter-timing": value, "--motion-filter-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; case "opacity": return { "--motion-opacity-timing": value, "--motion-opacity-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; case "background": return { "--motion-background-color-timing": value, "--motion-background-color-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; case "text": return { "--motion-text-color-timing": value, "--motion-text-color-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; default: if (isSpringWithBounce) { return { "--motion-timing": value, "--motion-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, // filter, opacity, and color animations don't look good with bouncy springs // so we use a smooth spring for them "--motion-filter-timing": "var(--motion-spring-smooth)", "--motion-opacity-timing": "var(--motion-spring-smooth)", "--motion-background-color-timing": "var(--motion-spring-smooth)", "--motion-text-color-timing": "var(--motion-spring-smooth)", "--motion-filter-perceptual-duration-multiplier": "1.66", "--motion-opacity-perceptual-duration-multiplier": "1.66", "--motion-background-color-perceptual-duration-multiplier": "1.66", "--motion-text-color-perceptual-duration-multiplier": "1.66", }; } else { return { "--motion-timing": value, "--motion-perceptual-duration-multiplier": `${perceptualDurationMultiplier}`, }; } } }, }, { values: theme("motionTimingFunction"), modifiers: { scale: "scale", translate: "translate", rotate: "rotate", blur: "blur", grayscale: "grayscale", opacity: "opacity", background: "background", text: "text", }, } ); // animation play state addUtilities({ ".motion-paused": { animationPlayState: "paused", "&::before": { animationPlayState: "paused", }, "&::after": { animationPlayState: "paused", }, }, ".motion-running": { animationPlayState: "running", "&::before": { animationPlayState: "running", }, "&::after": { animationPlayState: "running", }, }, }); // loop matchUtilities( { "motion-loop": (value: string, { modifier }: UtilityOptions) => { switch (modifier) { case "scale": return { "--motion-scale-loop-count": value } as CSSRuleObject; case "translate": return { "--motion-translate-loop-count": value }; case "rotate": return { "--motion-rotate-loop-count": value }; case "blur": case "grayscale": return { "--motion-filter-loop-count": value }; case "opacity": return { "--motion-opacity-loop-count": value }; case "background": return { "--motion-background-color-loop-count": value }; case "text": return { "--motion-text-color-loop-count": value }; default: return { "--motion-loop-count": value, }; } }, }, { values: theme("motionLoopCount"), modifiers: { scale: "scale", translate: "translate", rotate: "rotate", blur: "blur", grayscale: "grayscale", opacity: "opacity", background: "background", text: "text", }, } ); } export const modifiersTheme = { motionTimingFunction: ( theme: (path: keyof ThemeConfig) => Record<string, string> ) => ({ ...theme("transitionTimingFunction"), "spring-smooth": "var(--motion-spring-smooth)", "spring-snappy": "var(--motion-spring-snappy)", "spring-bouncy": "var(--motion-spring-bouncy)", "spring-bouncier": "var(--motion-spring-bouncier)", "spring-bounciest": "var(--motion-spring-bounciest)", bounce: "var(--motion-bounce)", "in-quad": "cubic-bezier(.55, .085, .68, .53)", "in-cubic": "cubic-bezier(.550, .055, .675, .19)", "in-quart": "cubic-bezier(.895, .03, .685, .22)", "in-back": "cubic-bezier(0.6,-0.28,0.74,0.05)", "out-quad": "cubic-bezier(.25, .46, .45, .94)", "out-cubic": "cubic-bezier(.215, .61, .355, 1)", "out-quart": "cubic-bezier(.165, .84, .44, 1)", "out-back": "cubic-bezier(0.18,0.89,0.32,1.27)", "in-out-quad": "cubic-bezier(.455, .03, .515, .955)", "in-out-cubic": "cubic-bezier(.645, .045, .355, 1)", "in-out-quart": "cubic-bezier(.77, 0, .175, 1)", "in-out-back": "cubic-bezier(0.68,-0.55,0.27,1.55)", }), motionDuration: ( theme: (path: keyof ThemeConfig) => Record<string, string> ) => ({ ...theme("transitionDuration"), 1500: "1500ms", 2000: "2000ms", DEFAULT: "750ms", }), motionDelay: ( theme: (path: keyof ThemeConfig) => Record<string, string> ) => ({ ...theme("motionDuration"), DEFAULT: "0ms", }), motionLoopCount: { infinite: "infinite", once: "1", twice: "2", }, };