playback-control
کنترل سرعت، جهت، مکث، اسکراب و تکرار انیمیشنها با الگوهای آماده
این skill برای ساخت کنترلهای تعاملی پخش انیمیشن طراحی شده است؛ از play و pause و تغییر سرعت تا reverse، alternate، looping، seeking و scrubbing دقیق. توکنهای آماده برای سرعت، جهت، تعداد تکرار و اندازهٔ گام، پیادهسازی را منسجمتر میکنند.
با نمونههای کاربردی برای Web Animations API، GSAP، Framer Motion و Remotion، میتوانید کنترل timeline، اسلایدر پیشرفت، slow motion و مدیریت state را سریعتر بسازید. پشتیبانی از prefers-reduced-motion نیز به تجربهای دسترسپذیرتر کمک میکند.
چه چیزی میگیرید
- توکنهای آماده برای سرعتهای paused، slow-motion، normal، double و rapid
- الگوهای کنترل جهت، reverse، alternate و تعداد تکرار انیمیشن
- نمونهٔ پیادهسازی scrubbing با mouse، touch، scroll و slider
- روشهای seeking، حرکت فریمبهفریم و پرش به زمان یا label
- نمونههای مدیریت state و تغییر نرم سرعت در Web Animations API، GSAP، Framer Motion و Remotion
برای چه کاری خوب است
- توسعهدهندگان رابطهای تعاملی با کنترل play، pause، سرعت و progress
- ساخت timelineهای قابل اسکراب برای انیمیشنهای GSAP یا ویدیوهای Remotion
- پروژههایی که به reverse، ping-pong، slow motion یا loopهای قابل تنظیم نیاز دارند
- طراحی کنترلهای دسترسیپذیر با پشتیبانی از prefers-reduced-motion
برای چه کاری نیست
- وقتی فقط به تعریف keyframe یا easing ساده و بدون کنترل تعاملی نیاز دارید
- پروژههایی که مدیریت کامل timeline یا پخش ویدیو خارج از scope انیمیشن رابط کاربری است
- وقتی انیمیشن ثابت است و نیازی به تغییر سرعت، جهت، زمان یا تکرار ندارد
دربارهٔ این اسکیل
Playback Control
Interactive control over animation playback.
Standards Reference
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| Web Animations API | developer.mozilla.org/Web/API/Web_Animations_API |
| CSS animation-* | developer.mozilla.org/CSS/animation |
| GSAP Timeline | gsap.com/docs/v3/GSAP/Timeline |
Quick Reference
Load from: tokens/playback.tokens.json
Speed Multipliers
| Token | Value | Use |
|---|---|---|
speed.paused | 0 | Frozen |
speed.slow-motion | 0.25 | Dramatic slow-mo |
speed.half | 0.5 | Gentle slow |
speed.normal | 1 | Standard |
speed.double | 2 | Fast forward |
speed.rapid | 4 | Very fast |
Direction Values
| Token | Value | Use |
|---|---|---|
direction.forward | 1 | Normal play |
direction.reverse | -1 | Backwards |
direction.alternate | "alternate" | Ping-pong |
Iteration Counts
| Token | Value | Use |
|---|---|---|
iteration.once | 1 | Play once |
iteration.twice | 2 | Play twice |
iteration.infinite | -1 | Loop forever |
Selection Guide
What control is needed?
│
├── SPEED CONTROL
│ ├── Slow motion → speed.slow-motion (0.25x)
│ ├── Normal → speed.normal (1x)
│ ├── Fast forward → speed.double (2x)
│ └── Pause → speed.paused (0)
│
├── DIRECTION CONTROL
│ ├── Forward → direction.forward
│ ├── Reverse → direction.reverse
│ └── Ping-pong → direction.alternate
│
├── LOOPING
│ ├── Once → iteration.once
│ ├── Fixed count → iteration.twice/thrice
│ └── Infinite → iteration.infinite
│
├── SCRUBBING
│ ├── Precise → scrub.sensitivity.low
│ ├── Normal → scrub.sensitivity.normal
│ └── Fast → scrub.sensitivity.high
│
└── SEEKING
├── Frame step → seek.step-frame
├── Small step → seek.step-small (0.1s)
└── Large step → seek.step-large (1s)
Patterns
Speed Control
Dynamically adjust animation playback speed.
Web Animations API:
// Create animation
const animation = element.animate(
[
{ transform: 'translateX(0)' },
{ transform: 'translateX(300px)' }
],
{ duration: 1000, fill: 'forwards' }
);
// Speed control functions
function setSpeed(rate) {
animation.playbackRate = rate; // {motion.playback.speed.*}
}
function slowMotion() {
setSpeed(0.25); // {motion.playback.speed.slow-motion}
}
function normalSpeed() {
setSpeed(1); // {motion.playback.speed.normal}
}
function fastForward() {
setSpeed(2); // {motion.playback.speed.double}
}
function pause() {
animation.pause();
}
function play() {
animation.play();
}
GSAP:
const tl = gsap.timeline();
tl.to(element, {
x: 300,
duration: 1,
ease: 'power2.out'
});
// Speed control
tl.timeScale(0.25); // {motion.playback.speed.slow-motion}
tl.timeScale(1); // {motion.playback.speed.normal}
tl.timeScale(2); // {motion.playback.speed.double}
// Pause/resume
tl.pause();
tl.resume();
tl.play();
Framer Motion:
import { motion, useAnimationControls } from 'framer-motion';
import { useState } from 'react';
const PlaybackControl = () => {
const controls = useAnimationControls();
const [speed, setSpeed] = useState(1);
const startAnimation = async () => {
await controls.start({
x: 300,
transition: {
duration: 1 / speed, // Adjust duration for speed
ease: [0.2, 0, 0, 1]
}
});
};
return (
<>
<motion.div animate={controls} />
<button onClick={() => setSpeed(0.25)}>Slow Motion</button>
<button onClick={() => setSpeed(1)}>Normal</button>
<button onClick={() => setSpeed(2)}>Fast</button>
<button onClick={startAnimation}>Play</button>
</>
);
};
Direction Control
Reverse and alternate animation direction.
Web Animations API:
const animation = element.animate(keyframes, {
duration: 1000,
iterations: Infinity,
direction: 'alternate' // {motion.playback.direction.alternate}
});
// Reverse current animation
function reverse() {
animation.reverse();
}
// Set direction explicitly
function setDirection(dir) {
// Recreate with new direction
animation.effect.updateTiming({ direction: dir });
}
CSS:
/* Alternate direction */
.ping-pong {
animation: slide 1s ease-in-out infinite alternate;
/* {motion.playback.direction.alternate} */
}
/* Reverse animation */
.reverse {
animation-direction: reverse;
/* {motion.playback.direction.reverse} */
}
@keyframes slide {
from { transform: translateX(0); }
to { transform: translateX(300px); }
}
GSAP:
const tl = gsap.timeline({ yoyo: true, repeat: -1 });
// yoyo = alternate direction
// Reverse the timeline
tl.reverse();
// Play forward
tl.play();
// Toggle direction
function toggleDirection() {
tl.reversed(!tl.reversed());
}
Interactive Scrubbing
Scrub animation based on user input.
GSAP + Scroll:
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.container',
start: 'top top',
end: 'bottom bottom',
scrub: true, // Smooth scrubbing
// scrub: 0.5 for slight delay
}
});
tl.to('.element', {
x: 300,
rotation: 360,
duration: 1
});
GSAP + Mouse/Touch:
const tl = gsap.timeline({ paused: true });
tl.to('.element', {
x: 300,
duration: 1
});
// Scrub with horizontal mouse position
document.addEventListener('mousemove', (e) => {
const progress = e.clientX / window.innerWidth;
tl.progress(progress);
});
// Scrub with slider
const slider = document.querySelector('#scrubber');
slider.addEventListener('input', (e) => {
const progress = e.target.value / 100;
tl.progress(progress);
});
Web Animations API:
const animation = element.animate(keyframes, {
duration: 1000,
fill: 'both'
});
animation.pause();
// Scrub with slider
slider.addEventListener('input', (e) => {
const progress = e.target.value / 100;
animation.currentTime = progress * animation.effect.getTiming().duration;
});
Framer Motion + Scroll:
import { motion, useScroll, useTransform } from 'framer-motion';
const ScrollScrub = () => {
const { scrollYProgress } = useScroll();
const x = useTransform(scrollYProgress, [0, 1], [0, 300]);
const rotate = useTransform(scrollYProgress, [0, 1], [0, 360]);
return (
<motion.div style={{ x, rotate }} />
);
};
Frame Stepping
Step through animation frame by frame.
Web Animations API:
const animation = element.animate(keyframes, {
duration: 1000,
fill: 'both'
});
animation.pause();
const frameStep = 33; // {motion.playback.seek.step-frame} ~30fps
function stepForward() {
animation.currentTime = Math.min(
animation.currentTime + frameStep,
animation.effect.getTiming().duration
);
}
function stepBackward() {
animation.currentTime = Math.max(
animation.currentTime - frameStep,
0
);
}
// Seek to specific percentage
function seekTo(percent) {
animation.currentTime = (percent / 100) * animation.effect.getTiming().duration;
}
GSAP:
const tl = gsap.timeline({ paused: true });
function stepForward() {
const stepSize = 0.033; // {motion.playback.seek.step-frame}
tl.progress(Math.min(tl.progress() + stepSize / tl.duration(), 1));
}
function stepBackward() {
const stepSize = 0.033;
tl.progress(Math.max(tl.progress() - stepSize / tl.duration(), 0));
}
// Seek to time
function seekToTime(seconds) {
tl.seek(seconds);
}
// Seek to label
function seekToLabel(label) {
tl.seek(label);
}
Playback State Management
Track and manage animation state.
JavaScript:
class AnimationController {
constructor(animation) {
this.animation = animation;
this.state = 'idle'; // {motion.playback.state.idle}
this.speed = 1;
this.direction = 1;
this.animation.onfinish = () => {
this.state = 'finished'; // {motion.playback.state.finished}
this.onStateChange?.(this.state);
};
}
play() {
this.animation.play();
this.state = 'running'; // {motion.playback.state.running}
this.onStateChange?.(this.state);
}
pause() {
this.animation.pause();
this.state = 'paused'; // {motion.playback.state.paused}
this.onStateChange?.(this.state);
}
toggle() {
if (this.state === 'running') {
this.pause();
} else {
this.play();
}
}
setSpeed(rate) {
this.speed = rate;
this.animation.playbackRate = rate * this.direction;
}
reverse() {
this.direction *= -1;
this.animation.playbackRate = this.speed * this.direction;
}
reset() {
this.animation.currentTime = this.direction > 0 ? 0 : this.animation.effect.getTiming().duration;
this.state = 'idle';
this.onStateChange?.(this.state);
}
}
React Hook:
import { useCallback, useRef, useState } from 'react';
type PlaybackState = 'idle' | 'running' | 'paused' | 'finished';
function usePlaybackControl() {
const animationRef = useRef<Animation | null>(null);
const [state, setState] = useState<PlaybackState>('idle');
const [speed, setSpeedState] = useState(1);
const play = useCallback(() => {
animationRef.current?.play();
setState('running');
}, []);
const pause = useCallback(() => {
animationRef.current?.pause();
setState('paused');
}, []);
const setSpeed = useCallback((rate: number) => {
if (animationRef.current) {
animationRef.current.playbackRate = rate;
}
setSpeedState(rate);
}, []);
const seek = useCallback((progress: number) => {
if (animationRef.current) {
const duration = animationRef.current.effect?.getTiming().duration as number;
animationRef.current.currentTime = progress * duration;
}
}, []);
return {
animationRef,
state,
speed,
play,
pause,
setSpeed,
seek
};
}
Smooth Speed Transitions
Gradually change speed instead of instant jumps.
JavaScript:
function smoothSpeedChange(animation, targetSpeed, duration = 200) {
// {motion.playback.transition-speed.smooth}
const startSpeed = animation.playbackRate;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Ease-out interpolation
const eased = 1 - Math.pow(1 - progress, 3);
animation.playbackRate = startSpeed + (targetSpeed - startSpeed) * eased;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
// Usage
smoothSpeedChange(animation, 0.25); // Smooth transition to slow-mo
Remotion Implementation
import { useCurrentFrame, interpolate, Sequence } from 'remotion';
// Playback speed is controlled via frame mapping
export const VariableSpeed: React.FC<{ speedCurve: number[] }> = ({ speedCurve }) => {
const frame = useCurrentFrame();
// Map frame to adjusted time based on speed curve
// speedCurve: [1, 1, 0.5, 0.5, 2, 2] means:
// normal, normal, half-speed, half-speed, double, double
let adjustedTime = 0;
for (let i = 0; i < Math.min(frame, speedCurve.length); i++) {
adjustedTime += speedCurve[i] || 1;
}
// Use adjustedTime for animation calculations
const x = interpolate(adjustedTime, [0, 100], [0, 300]);
return (
<div style={{ transform: `translateX(${x}px)` }} />
);
};
// Scrubbing in Remotion Preview
// Use the timeline scrubber in the Remotion Studio
Reduced Motion
For prefers-reduced-motion: reduce:
| Full Motion | Reduced Alternative |
|---|---|
| Speed variations | Single normal speed only |
| Slow motion | Instant or fast transition |
| Looping | Play once |
| Scrubbing | Jump to positions |
Implementation:
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
function getPlaybackConfig() {
if (prefersReducedMotion) {
return {
speed: 10, // {motion.playback.speed.instant} - near instant
iterations: 1, // {motion.playback.iteration.once}
direction: 'normal'
};
}
return {
speed: 1,
iterations: Infinity,
direction: 'alternate'
};
}
Cross-References
- See
looping/for loop configuration details - See
sequencing/for timeline orchestration - See
duration/for timing values - See
scroll-animations/for scroll-based scrubbing - See
gesture-motion/for touch-based control - See
lottie/for Lottie player control - See
remotion/for video timeline control
References
- No additional references needed for this skill