|
1 | 1 | import { type ChangeEvent, type MouseEventHandler, useState } from 'react' |
| 2 | +import { useMotionValue, useSpring, useMotionValueEvent } from 'motion/react' |
2 | 3 | import AvatarEditor, { |
3 | 4 | type Position, |
4 | 5 | useAvatarEditor, |
@@ -62,6 +63,23 @@ const App = () => { |
62 | 63 | const update = (patch: Partial<State>) => |
63 | 64 | setState((s) => ({ ...s, ...patch })) |
64 | 65 |
|
| 66 | + // Animate rotation with a spring for smooth transitions |
| 67 | + const rotateMotion = useMotionValue(0) |
| 68 | + const rotateSpring = useSpring(rotateMotion, { stiffness: 200, damping: 25 }) |
| 69 | + const [animatedRotate, setAnimatedRotate] = useState(0) |
| 70 | + const [isRotating, setIsRotating] = useState(false) |
| 71 | + |
| 72 | + useMotionValueEvent(rotateSpring, 'change', (v) => setAnimatedRotate(v)) |
| 73 | + useMotionValueEvent(rotateSpring, 'animationStart', () => setIsRotating(true)) |
| 74 | + useMotionValueEvent(rotateSpring, 'animationComplete', () => |
| 75 | + setIsRotating(false), |
| 76 | + ) |
| 77 | + |
| 78 | + // Sync spring target when state.rotate changes |
| 79 | + if (rotateMotion.get() !== state.rotate) { |
| 80 | + rotateMotion.set(state.rotate) |
| 81 | + } |
| 82 | + |
65 | 83 | const handleSave = () => { |
66 | 84 | const img = editor.getImageScaledToCanvas()?.toDataURL() |
67 | 85 | const rect = editor.getCroppingRect() |
@@ -115,7 +133,7 @@ const App = () => { |
115 | 133 | width={state.width} |
116 | 134 | height={state.height} |
117 | 135 | position={state.position} |
118 | | - rotate={state.rotate} |
| 136 | + rotate={animatedRotate} |
119 | 137 | borderRadius={state.width / (100 / state.borderRadius)} |
120 | 138 | backgroundColor={state.backgroundColor} |
121 | 139 | showGrid={state.showGrid} |
@@ -168,7 +186,8 @@ const App = () => { |
168 | 186 | {/* Rotation */} |
169 | 187 | <div className="control-group"> |
170 | 188 | <div className="control-label"> |
171 | | - Rotation <span className="value">{state.rotate}°</span> |
| 189 | + Rotation{' '} |
| 190 | + <span className="value">{Math.round(animatedRotate)}°</span> |
172 | 191 | </div> |
173 | 192 | <div className="inline-row"> |
174 | 193 | <input |
@@ -340,7 +359,11 @@ const App = () => { |
340 | 359 | <span className="toggle-track" /> |
341 | 360 | <span className="toggle-text">Lock canvas rotation</span> |
342 | 361 | </label> |
343 | | - <button className="btn btn-primary" onClick={handleSave}> |
| 362 | + <button |
| 363 | + className="btn btn-primary" |
| 364 | + onClick={handleSave} |
| 365 | + disabled={isRotating} |
| 366 | + > |
344 | 367 | Export Preview |
345 | 368 | </button> |
346 | 369 | </div> |
|
0 commit comments