{
    "slug": "js_requestanimationframe",
    "term": "requestAnimationFrame — Smooth Animations",
    "category": "javascript",
    "difficulty": "intermediate",
    "short": "requestAnimationFrame(callback) schedules a function to run before the browser's next repaint — the correct way to animate in JavaScript, producing smooth 60fps motion and automatically pausing when the tab is hidden.",
    "long": "Before requestAnimationFrame, animations used setTimeout or setInterval with fixed millisecond delays. These ignore the monitor's refresh rate, fire at inconsistent intervals, and continue running in background tabs wasting CPU. requestAnimationFrame synchronises with the browser's render cycle — typically 60fps on most displays, 120fps on high-refresh screens. The callback receives a DOMHighResTimeStamp argument giving the current time, enabling frame-rate-independent animation. When the tab is not visible, the browser throttles or stops rAF calls entirely. cancelAnimationFrame() cancels a pending callback. For complex animations, the Web Animations API or CSS transitions are often preferable — they can run on the compositor thread without blocking the main thread.",
    "aliases": [
        "requestAnimationFrame",
        "rAF",
        "animation loop",
        "browser repaint"
    ],
    "tags": [
        "javascript",
        "animation",
        "performance",
        "browser",
        "canvas"
    ],
    "misconception": "requestAnimationFrame guarantees exactly 60fps. It runs once per repaint cycle — on a 144Hz monitor that is 144 times per second; on a throttled background tab it may run at 1fps. Write animations based on the elapsed time argument, not on assumed frame rate.",
    "why_it_matters": "setTimeout-based animation produces visible jank because it fires independently of the display's refresh cycle — frames are dropped or doubled. requestAnimationFrame ties animation to the actual screen refresh, producing smooth motion. It also enables automatic performance optimisation: pausing when the tab is not visible saves battery and CPU.",
    "common_mistakes": [
        "Not using the timestamp argument — hardcoding speed as 'pixels per frame' makes animation fast on 144Hz and slow on 30Hz; always use elapsed time.",
        "Forgetting to store and cancel the animation ID — rAF loops that are never cancelled continue running even after components are removed, causing memory leaks.",
        "Calling rAF from inside a resize event handler — creates multiple parallel animation loops; keep one loop and handle resize state separately.",
        "Doing heavy computation inside the rAF callback — long tasks block the main thread and skip frames; offload to Web Workers and only do drawing inside rAF."
    ],
    "when_to_use": [],
    "avoid_when": [],
    "related": [
        "js_canvas_api",
        "js_performance_api",
        "js_web_workers",
        "js_event_loop_blocking",
        "css_animations"
    ],
    "prerequisites": [],
    "refs": [
        "https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"
    ],
    "bad_code": "// ❌ setInterval animation — ignores refresh rate, runs in background\nlet x = 0;\nsetInterval(() => {\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    ctx.fillRect(x++, 50, 20, 20);\n    // Runs every 16ms regardless of monitor refresh rate\n    // Continues running in background tab — wastes battery\n    // No sync with display — produces tearing/jank\n}, 16);",
    "good_code": "// ✅ requestAnimationFrame — synced to display, time-based movement\nlet x = 0;\nlet lastTime = null;\nlet animId = null;\n\nfunction draw(timestamp) {\n    if (lastTime === null) lastTime = timestamp;\n    const elapsed = timestamp - lastTime; // ms since last frame\n    lastTime = timestamp;\n\n    const speed = 200; // pixels per second — frame-rate independent\n    x += speed * (elapsed / 1000);\n\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    ctx.fillRect(x % canvas.width, 50, 20, 20);\n\n    animId = requestAnimationFrame(draw);\n}\n\n// Start\nanimId = requestAnimationFrame(draw);\n\n// Stop when needed (e.g. component unmount)\nfunction stop() { cancelAnimationFrame(animId); }",
    "quick_fix": "Replace setInterval(draw, 16) with function loop(t) { draw(t); requestAnimationFrame(loop); } requestAnimationFrame(loop). Use the timestamp argument for time-based animation instead of assuming fixed frame rate.",
    "effort": "low",
    "created": "2026-03-23",
    "updated": "2026-04-05",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/js_requestanimationframe",
        "html_url": "https://codeclaritylab.com/glossary/js_requestanimationframe",
        "json_url": "https://codeclaritylab.com/glossary/js_requestanimationframe.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[requestAnimationFrame — Smooth Animations](https://codeclaritylab.com/glossary/js_requestanimationframe) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/js_requestanimationframe"
            }
        }
    }
}