Table of Contents
▼Animasi CSS tradisional punya keterbatasan besar: mereka nggak bisa reaktif terhadap state aplikasi secara real-time tanpa JavaScript yang berat.
Ketika kamu bikin button yang harus berubah warna berdasarkan posisi cursor, atau form yang animasinya berubah sesuai validasi input, CSS animation standar mulai kewalahan.
Props for That library hadir sebagai solusi elegan untuk masalah ini.
Library ini memungkinkan kamu membuat animasi yang benar-benar reaktif terhadap props React, tanpa harus nulis ratusan baris JavaScript untuk handle setiap perubahan state.
Keterbatasan CSS Animation untuk State Dinamis
CSS Animation dan CSS Transition punya kelemahan fundamental ketika berurusan dengan state yang berubah-ubah.
Mereka dirancang untuk transisi yang sederhana dan linear.
Kalau kamu pernah coba bikin animasi yang harus berubah berdasarkan posisi mouse, kamu pasti tahu betapa frustrasinya.
Kamu harus pakai JavaScript untuk track posisi cursor, update CSS variables, dan pastikan performa tetap smooth.
Belum lagi kalau animasi harus bereaksi terhadap multiple state sekaligus.
Contoh kasusnya: button yang harus glow lebih terang ketika cursor mendekat, tapi juga harus berubah warna ketika form invalid.
Dengan CSS murni, kamu butuh kombinasi kompleks dari pseudo-classes, custom properties, dan JavaScript yang nge-update values secara manual.
Hasilnya? Kode yang susah di-maintain dan performa yang kadang-kadang jadi bottleneck.
Apa Itu Prop Based Animation dan Props for That
Prop-based animation adalah paradigma di mana animasi di-drive langsung oleh props atau state component React.
Bukan lewat class toggles atau inline styles yang di-inject manual.
Props for That adalah library yang built on top of Framer Motion, tapi dengan API yang lebih sederhana dan fokus pada reaktivitas terhadap props.
Library ini memungkinkan kamu mendefinisikan animasi yang secara otomatis bereaksi ketika props berubah.
Konsep dasarnya sederhana: kamu declare animation states berdasarkan nilai props, dan library yang handle interpolation dan timing.
import { motion } from 'framer-motion';
const AnimatedButton = ({ isActive, cursorDistance }) => {
return (
<motion.button
animate={{
scale: isActive ? 1.1 : 1,
boxShadow: `0 0 ${20 - cursorDistance}px rgba(59, 130, 246, 0.5)`
}}
transition={{ type: "spring", stiffness: 300 }}
>
Click Me
</motion.button>
);
};
Contoh di atas menunjukkan bagaimana button bisa bereaksi terhadap dua props sekaligus tanpa logika kompleks.
Library akan smooth-transition antara states secara otomatis.
Butuh jasa pembuatan website profesional? KerjaKode menyediakan layanan pembuatan website berkualitas tinggi dengan harga terjangkau. Kunjungi jasa pembuatan website KerjaKode untuk konsultasi gratis dan wujudkan website impian Anda.
Implementasi Cursor Reactive Component di React
Cursor-reactive animation adalah salah satu use case paling populer untuk prop-based animation.
Bayangkan navigation bar yang elementsnya bereaksi ketika cursor mendekat, atau card yang sedikit tilt mengikuti posisi mouse.
Pertama, kita perlu track posisi cursor relative terhadap element.
import { useState, useRef, useEffect } from 'react';
import { motion, useMotionValue, useTransform } from 'framer-motion';
const CursorReactiveCard = ({ children }) => {
const cardRef = useRef(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
useEffect(() => {
const card = cardRef.current;
if (!card) return;
const handleMouseMove = (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
mouseX.set(x);
mouseY.set(y);
};
card.addEventListener('mousemove', handleMouseMove);
return () => card.removeEventListener('mousemove', handleMouseMove);
}, []);
const rotateX = useTransform(mouseY, [0, 300], [10, -10]);
const rotateY = useTransform(mouseX, [0, 300], [-10, 10]);
return (
<motion.div
ref={cardRef}
style={{
rotateX,
rotateY,
transformStyle: 'preserve-3d'
}}
className="card-container"
>
{children}
</motion.div>
);
};
Code di atas membuat card yang tilt mengikuti posisi cursor dengan smooth interpolation.
useMotionValue menyimpan nilai yang bisa di-animate tanpa trigger re-render.
useTransform melakukan mapping dari range input ke range output secara otomatis.
Untuk implementasi yang lebih advanced, kamu bisa tambahkan magnetic effect.
const MagneticButton = ({ children, strength = 0.3 }) => {
const buttonRef = useRef(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
useEffect(() => {
const button = buttonRef.current;
if (!button) return;
const handleMouseMove = (e) => {
const rect = button.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const deltaX = (e.clientX - centerX) * strength;
const deltaY = (e.clientY - centerY) * strength;
x.set(deltaX);
y.set(deltaY);
};
const handleMouseLeave = () => {
x.set(0);
y.set(0);
};
button.addEventListener('mousemove', handleMouseMove);
button.addEventListener('mouseleave', handleMouseLeave);
return () => {
button.removeEventListener('mousemove', handleMouseMove);
button.removeEventListener('mouseleave', handleMouseLeave);
};
}, [strength]);
return (
<motion.button
ref={buttonRef}
style={{ x, y }}
transition={{ type: "spring", stiffness: 150, damping: 15 }}
>
{children}
</motion.button>
);
};
Button ini akan "tertarik" ke arah cursor dengan efek spring yang natural.
Parameter strength mengontrol seberapa kuat magnetic effect-nya.
Form State Animation Tanpa JavaScript Heavy
Form adalah area di mana prop-based animation benar-benar shine.
Kamu bisa membuat feedback visual yang kaya tanpa bloat code yang biasanya datang dengan custom form validation animation.
Bayangkan input field yang berubah appearance berdasarkan validation state.
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
const AnimatedFormInput = ({
label,
type = "text",
validation = null
}) => {
const [value, setValue] = useState('');
const [isFocused, setIsFocused] = useState(false);
const [isValid, setIsValid] = useState(null);
const handleChange = (e) => {
const newValue = e.target.value;
setValue(newValue);
if (validation) {
setIsValid(validation(newValue));
}
};
const getBorderColor = () => {
if (!isFocused && isValid === false) return '#ef4444';
if (isValid === true) return '#10b981';
if (isFocused) return '#3b82f6';
return '#d1d5db';
};
return (
<div className="form-group">
<motion.div
animate={{
borderColor: getBorderColor(),
boxShadow: isFocused
? `0 0 0 3px ${getBorderColor()}20`
: '0 0 0 0px transparent'
}}
transition={{ duration: 0.2 }}
className="input-wrapper"
>
<input
type={type}
value={value}
onChange={handleChange}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder=" "
/>
<motion.label
animate={{
y: value || isFocused ? -24 : 0,
scale: value || isFocused ? 0.85 : 1,
color: getBorderColor()
}}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
{label}
</motion.label>
</motion.div>
<AnimatePresence>
{isValid === false && (
<motion.span
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="error-message"
>
Invalid input
</motion.span>
)}
</AnimatePresence>
</div>
);
};
// Usage
const EmailForm = () => {
const emailValidation = (value) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
};
return (
<AnimatedFormInput
label="Email Address"
type="email"
validation={emailValidation}
/>
);
};
Component ini memberikan real-time visual feedback tanpa perlu banyak state management.
Border color, shadow, dan label position semuanya bereaksi terhadap fokus dan validation state.
AnimatePresence memungkinkan error message muncul dan hilang dengan smooth transition.
Untuk form yang lebih kompleks dengan multiple steps, kamu bisa pakai variant system.
const MultiStepForm = () => {
const [step, setStep] = useState(0);
const formVariants = {
enter: (direction) => ({
x: direction > 0 ? 300 : -300,
opacity: 0
}),
center: {
x: 0,
opacity: 1
},
exit: (direction) => ({
x: direction > 0 ? -300 : 300,
opacity: 0
})
};
const steps = [
<PersonalInfo key="personal" />,
<ContactInfo key="contact" />,
<Preferences key="preferences" />
];
return (
<div className="form-container">
<AnimatePresence mode="wait" custom={step}>
<motion.div
key={step}
custom={step}
variants={formVariants}
initial="enter"
animate="center"
exit="exit"
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
{steps[step]}
</motion.div>
</AnimatePresence>
<div className="navigation">
{step > 0 && (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setStep(step - 1)}
>
Previous
</motion.button>
)}
{step < steps.length - 1 && (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setStep(step + 1)}
>
Next
</motion.button>
)}
</div>
</div>
);
};
Multi-step form ini punya transition yang smooth antara steps dengan direction-aware animation.
Performance Optimization untuk Mobile Device
Animasi yang smooth di desktop belum tentu smooth di mobile.
Device dengan CPU dan GPU terbatas butuh optimization khusus.
First rule: pakai transform dan opacity properties, bukan width, height, atau properties lain yang trigger layout recalculation.
// BAD - Triggers layout recalculation
<motion.div animate={{ width: isOpen ? 300 : 0 }} />
// GOOD - Uses GPU-accelerated transform
<motion.div
animate={{ scaleX: isOpen ? 1 : 0 }}
style={{ transformOrigin: 'left' }}
/>
Transform dan opacity di-handle oleh GPU, sementara width dan height butuh CPU untuk recalculate layout.
Untuk mobile, reduce complexity dari animation.
import { useReducedMotion } from 'framer-motion';
const ResponsiveAnimation = ({ children }) => {
const shouldReduceMotion = useReducedMotion();
const variants = {
hidden: {
opacity: 0,
y: shouldReduceMotion ? 0 : 20
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: shouldReduceMotion ? 0.1 : 0.5,
ease: shouldReduceMotion ? 'linear' : 'easeOut'
}
}
};
return (
<motion.div
variants={variants}
initial="hidden"
animate="visible"
>
{children}
</motion.div>
);
};
useReducedMotion hook mendeteksi user preference untuk reduced motion.
Ini penting untuk accessibility dan juga membantu performa di low-end devices.
Untuk cursor-reactive animations, disable mereka di touch devices.
const SmartCursorReactive = ({ children }) => {
const [isTouch, setIsTouch] = useState(false);
useEffect(() => {
const checkTouch = () => {
setIsTouch('ontouchstart' in window || navigator.maxTouchPoints > 0);
};
checkTouch();
}, []);
if (isTouch) {
return <div>{children}</div>;
}
return <CursorReactiveCard>{children}</CursorReactiveCard>;
};
Touch devices nggak punya concept "hover" yang meaningful, jadi cursor-reactive animation cuma waste resources.
Implement throttling untuk mouse events yang high-frequency.
import { throttle } from 'lodash-es';
import { useCallback } from 'react';
const OptimizedCursorTracking = () => {
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const handleMouseMove = useCallback(
throttle((e) => {
const rect = e.currentTarget.getBoundingClientRect();
mouseX.set(e.clientX - rect.left);
mouseY.set(e.clientY - rect.top);
}, 16), // ~60fps
[]
);
return (
<motion.div onMouseMove={handleMouseMove}>
{/* content */}
</motion.div>
);
};
Throttling ke 16ms (60fps) cukup untuk smooth animation tanpa overwhelm browser.
Use will-change CSS property dengan hati-hati.
const PerformantAnimation = () => {
const [isAnimating, setIsAnimating] = useState(false);
return (
<motion.div
style={{
willChange: isAnimating ? 'transform, opacity' : 'auto'
}}
onAnimationStart={() => setIsAnimating(true)}
onAnimationComplete={() => setIsAnimating(false)}
>
{/* content */}
</motion.div>
);
};
will-change memberi hint ke browser untuk optimize layer management, tapi jangan dipake permanently karena bisa waste memory.
Untuk list dengan banyak animated items, pakai layoutId untuk shared element transitions.
const AnimatedList = ({ items }) => {
return (
<motion.ul layout>
<AnimatePresence mode="popLayout">
{items.map(item => (
<motion.li
key={item.id}
layoutId={item.id}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
{item.content}
</motion.li>
))}
</AnimatePresence>
</motion.ul>
);
};
layoutId memungkinkan Framer Motion reuse animated elements ketika position berubah, dramatically improving performance.
Implement lazy loading untuk components dengan heavy animations.
import { lazy, Suspense } from 'react';
const HeavyAnimatedComponent = lazy(() =>
import('./HeavyAnimatedComponent')
);
const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyAnimatedComponent />
</Suspense>
);
};
Ini ensure animation code nggak block initial page load.
Monitor performance dengan React DevTools Profiler.
import { Profiler } from 'react';
const onRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
if (actualDuration > 16) {
console.warn(`Slow render in ${id}: ${actualDuration}ms`);
}
};
const ProfiledAnimation = () => {
return (
<Profiler id="animation" onRender={onRenderCallback}>
<AnimatedComponent />
</Profiler>
);
};
Renders yang makan waktu lebih dari 16ms (60fps threshold) perlu di-optimize.
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Prop-based animation dengan Props for That dan Framer Motion membuka possibilities baru untuk interactive web applications.
Kamu bisa bikin experiences yang sebelumnya butuh ratusan baris vanilla JavaScript dengan cara yang lebih declarative dan maintainable.
Yang penting adalah balance antara visual richness dan performance, especially untuk market Indonesia di mana banyak users pakai mid-range devices.
Start dengan simple cursor-reactive elements dan form animations, test di real devices, dan gradually add complexity ketika kamu confident dengan performance implications.
Animation bukan cuma soal making things look cool, tapi providing meaningful feedback yang improve user experience secara measurable.
Dengan approach yang tepat, prop-based animations bisa jadi differentiator yang bikin web app kamu stand out di market yang crowded.