|
| 1 | +import * as React from 'react' |
| 2 | +import { cubicCoordinates, stepsCoordinates } from 'easing-coordinates' |
| 3 | +import { useSpring, animated, to as interpolate, createInterpolator } from '@react-spring/web' |
| 4 | +import { useControls } from 'leva' |
| 5 | + |
| 6 | +import styles from './styles.module.css' |
| 7 | + |
| 8 | +const easeMap = { |
| 9 | + 'ease-in-out': { x1: 0.42, y1: 0, x2: 0.58, y2: 1 }, |
| 10 | + 'ease-out': { x1: 0, y1: 0, x2: 0.58, y2: 1 }, |
| 11 | + 'ease-in': { x1: 0.42, y1: 0, x2: 1, y2: 1 }, |
| 12 | + ease: { x1: 0.25, y1: 0.1, x2: 0.25, y2: 1 }, |
| 13 | + linear: { x1: 0.25, y1: 0.25, x2: 0.75, y2: 0.75 }, |
| 14 | +} |
| 15 | + |
| 16 | +export default function App() { |
| 17 | + const { from, mid, to, angle, stops, easeCustom, easing } = useControls({ |
| 18 | + from: '#0bd1ff', |
| 19 | + mid: '#ffa3ff', |
| 20 | + to: '#ffd34e', |
| 21 | + angle: { |
| 22 | + value: 32, |
| 23 | + min: 0, |
| 24 | + max: 360, |
| 25 | + }, |
| 26 | + stops: { |
| 27 | + value: 5, |
| 28 | + max: 100, |
| 29 | + min: 2, |
| 30 | + }, |
| 31 | + easing: { |
| 32 | + value: 'ease-in-out', |
| 33 | + options: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'steps'], |
| 34 | + }, |
| 35 | + easeCustom: '', |
| 36 | + }) |
| 37 | + |
| 38 | + const { colorFrom, colorMid, colorTo } = useSpring({ |
| 39 | + colorFrom: from, |
| 40 | + colorMid: mid, |
| 41 | + colorTo: to, |
| 42 | + }) |
| 43 | + |
| 44 | + const coordinates = React.useMemo(() => { |
| 45 | + let coordinates |
| 46 | + const customBezier = easeCustom.split(',').map(Number) |
| 47 | + if (customBezier.length <= 1) { |
| 48 | + if (easing === 'steps') { |
| 49 | + coordinates = stepsCoordinates(stops, 'skip-none') |
| 50 | + } else { |
| 51 | + const { x1, y1, x2, y2 } = easeMap[easing] |
| 52 | + coordinates = cubicCoordinates(x1, y1, x2, y2, stops) |
| 53 | + } |
| 54 | + } else { |
| 55 | + coordinates = cubicCoordinates(customBezier[0], customBezier[1], customBezier[2], customBezier[3], stops) |
| 56 | + } |
| 57 | + |
| 58 | + return coordinates |
| 59 | + }, [easing, easeCustom, stops]) |
| 60 | + |
| 61 | + const allStops = interpolate([colorFrom, colorMid, colorTo], (from, mid, to) => { |
| 62 | + const blend = createInterpolator({ range: [0, 0.5, 1], output: [from, mid, to] }) |
| 63 | + |
| 64 | + return coordinates.map(({ x, y }) => { |
| 65 | + const color = blend(y) |
| 66 | + |
| 67 | + return `${color} ${x * 100}%` |
| 68 | + }) |
| 69 | + }) |
| 70 | + |
| 71 | + return ( |
| 72 | + <animated.div |
| 73 | + className={styles.container} |
| 74 | + style={{ |
| 75 | + backgroundImage: allStops.to((...args) => `linear-gradient(${angle}deg, ${args.join(', ')})`), |
| 76 | + }} |
| 77 | + /> |
| 78 | + ) |
| 79 | +} |
0 commit comments