|

CSS 57 💻 animation properties part 2

This lesson covers the remaining animation properties: animation-fill-mode, animation-play-state, animation-timeline, and animation-composition.


Overview of Properties

PropertyDescriptionDefault
animation-fill-modeStyles before/after animationnone
animation-play-stateRunning or pausedrunning
animation-timelineTimeline that controls the animationauto
animation-compositionHow multiple animations combinereplace

1. animation-fill-mode

Defines the styles applied to an element before and after the animation plays.

.box {
    animation-fill-mode: forwards;
}

Values

ValueDescription
noneNo styles applied outside the animation (default)
forwardsKeep the last keyframe’s styles after the animation ends
backwardsApply the first keyframe’s styles during the delay
bothApply both forwards and backwards

Visual Explanation

Without fill-mode:
[before] [animation] [after → reverts to original]

With forwards:
[before] [animation] [after → keeps end state]

With backwards:
[before → shows start state] [animation] [after]

With both:
[before → shows start state] [animation] [after → keeps end state]

Key Points:

  • forwards — the element stays at the animation’s end state
  • backwards — the element shows the start state during the delay
  • both — combines both effects
  • Without fill-mode, the element snaps back to its original state after the animation

2. animation-play-state

Controls whether an animation is running or paused.

.box {
    animation-play-state: paused;
}

Values

ValueDescription
runningAnimation is playing (default)
pausedAnimation is paused

Key Points:

  • Useful for pausing animations on hover
  • The animation resumes from where it was paused
  • Commonly used with :hover for interactive control

Example:

.animation:hover {
    animation-play-state: paused;
}

3. animation-timeline

Allows you to specify a timeline that controls the animation instead of the default document timeline.

.box {
    animation-timeline: scroll();
}

Values

ValueDescription
autoDefault document timeline (default)
noneNo timeline — animation doesn’t play
scroll()Binds animation to scroll position
view()Binds animation to element visibility in viewport

scroll() Timeline

.box {
    animation-timeline: scroll();
}

The animation progresses in sync with scrolling — as the user scrolls, the animation advances. This creates scroll-driven animations that feel connected to the user’s interaction.

Variations:

animation-timeline: scroll();           /* Root scroller */
animation-timeline: scroll(root);       /* Root scroller */
animation-timeline: scroll(nearest);    /* Nearest scrollable ancestor */
animation-timeline: scroll(self);       /* The element itself */

view() Timeline

.box {
    animation-timeline: view();
}

The animation progresses based on the element’s visibility within the viewport — as the element enters, crosses, and exits the viewport, the animation plays.

Key Points:

  • animation-timeline is a modern feature with growing browser support
  • Scroll-driven animations are performant — they run on the compositor thread
  • Always provide a fallback for browsers that don’t support it

4. animation-composition

Controls how multiple animations that target the same properties combine their effects.

.box {
    animation-composition: add;
}

Values

ValueDescription
replaceNew animations overwrite previous ones (default)
addNew animations add to existing effects
accumulateSimilar to add, but with additional rules for keyframes and timing

How It Works

replace (default): The last animation wins.

.box {
    animation-name: moveRight, fadeOut;
    animation-composition: replace;
    /* fadeOut's opacity replaces any opacity from moveRight */
}

add: Effects are combined.

.box {
    animation-name: moveRight, moveUp;
    animation-composition: add;
    /* The box moves both right AND up */
}

accumulate: Similar to add, but keyframe values accumulate.

.box {
    animation-name: scale1, scale2;
    animation-composition: accumulate;
    /* scale1 and scale2 combine their scale values */
}

Key Points:

  • replace is the default — later animations override earlier ones
  • add combines transform effects (e.g., translate + rotate)
  • accumulate is useful for stacking animations with the same property

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animation Properties — Part 2</title>
    <style>
        *, *::before, *::after {
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            padding: 20px;
            background: #f8f9fa;
            color: #333;
            max-width: 1200px;
            margin: 0 auto;
            line-height: 1.6;
        }

        h1 {
            color: #007bff;
            border-bottom: 3px solid #007bff;
            padding-bottom: 10px;
        }

        h2 {
            color: #28a745;
            border-left: 4px solid #28a745;
            padding-left: 15px;
            margin-top: 30px;
        }

        h3 {
            color: #333;
            margin-top: 20px;
        }

        section {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin: 20px 0;
        }

        .code-block {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
            margin: 10px 0;
        }

        .reference-table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }

        .reference-table th,
        .reference-table td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }

        .reference-table th {
            background: #007bff;
            color: white;
        }

        .reference-table tr:nth-child(even) {
            background: #f8f9fa;
        }

        .note {
            font-size: 0.9rem;
            color: #6c757d;
            margin-top: 5px;
        }

        .highlight {
            background: #ffc107;
            color: #333;
            padding: 2px 6px;
            border-radius: 4px;
            font-weight: bold;
        }

        /* ====== DEMO AREA ====== */
        .demo-area {
            display: flex;
            flex-wrap: wrap;
            gap: 40px;
            justify-content: center;
            align-items: center;
            padding: 40px 20px;
            background: #e9ecef;
            border-radius: 8px;
            margin: 15px 0;
            min-height: 200px;
            overflow: hidden;
        }

        /* ====== BASIC ANIMATION ====== */
        .box {
            width: 100px;
            height: 100px;
            background: #007bff;
            border-radius: 12px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
            animation-name: exampleAnimation;
            animation-duration: 2s;
            animation-fill-mode: forwards;
        }

        @keyframes exampleAnimation {
            from {
                transform: translateX(0);
                background-color: #007bff;
            }
            to {
                transform: translateX(200px);
                background-color: #dc3545;
            }
        }

        /* ====== FILL MODE COMPARISON ====== */
        .fill-box {
            width: 80px;
            height: 80px;
            background: #007bff;
            border-radius: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: bold;
            font-size: 0.7rem;
            animation-name: fillDemo;
            animation-duration: 1s;
            animation-delay: 0.5s;
            animation-timing-function: ease;
        }

        .fill-none {
            animation-fill-mode: none;
        }

        .fill-forwards {
            animation-fill-mode: forwards;
        }

        .fill-backwards {
            animation-fill-mode: backwards;
        }

        .fill-both {
            animation-fill-mode: both;
        }

        @keyframes fillDemo {
            from {
                opacity: 0;
                transform: translateX(-50px);
                background: #dc3545;
            }
            to {
                opacity: 1;
                transform: translateX(0);
                background: #28a745;
            }
        }

        /* ====== PLAY STATE ====== */
        .play-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 15px;
        }

        .play-box {
            width: 100px;
            height: 100px;
            background: #6c5ce7;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: bold;
            animation: pulseAnim 1.5s ease-in-out infinite;
        }

        .play-box:hover {
            animation-play-state: paused;
        }

        @keyframes pulseAnim {
            0%, 100% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(108, 92, 231, 0.7);
            }
            50% {
                transform: scale(1.15);
                box-shadow: 0 0 0 15px rgba(108, 92, 231, 0);
            }
        }

        /* ====== COMPOSITION ====== */
        .composition-box {
            width: 80px;
            height: 80px;
            border-radius: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: bold;
            font-size: 0.6rem;
            animation-duration: 3s;
            animation-iteration-count: infinite;
            animation-timing-function: ease-in-out;
        }

        .comp-replace {
            background: #007bff;
            animation-name: moveRight, rotateBox;
            animation-composition: replace;
        }

        .comp-add {
            background: #28a745;
            animation-name: moveRight, moveUp;
            animation-composition: add;
        }

        .comp-accumulate {
            background: #ffc107;
            color: #333;
            animation-name: scaleUp, scaleDown;
            animation-composition: accumulate;
        }

        @keyframes moveRight {
            from { transform: translateX(0); }
            to   { transform: translateX(100px); }
        }

        @keyframes moveUp {
            from { transform: translateY(0); }
            to   { transform: translateY(-50px); }
        }

        @keyframes rotateBox {
            from { transform: rotate(0deg); }
            to   { transform: rotate(180deg); }
        }

        @keyframes scaleUp {
            from { transform: scale(1); }
            to   { transform: scale(1.5); }
        }

        @keyframes scaleDown {
            from { transform: scale(1); }
            to   { transform: scale(0.7); }
        }

        /* ====== SCROLL TIMELINE ====== */
        .scroll-container {
            height: 300px;
            overflow-y: scroll;
            background: #f8f9fa;
            border: 2px solid #ddd;
            border-radius: 8px;
            margin: 15px 0;
            position: relative;
        }

        .scroll-content {
            height: 1200px;
            padding: 20px;
        }

        .scroll-animated-box {
            width: 80px;
            height: 80px;
            background: linear-gradient(135deg, #007bff, #6c5ce7);
            border-radius: 12px;
            position: sticky;
            top: 20px;
            animation-name: scrollAnimation;
            animation-duration: 1s;
            animation-timeline: scroll(nearest);
            animation-fill-mode: both;
        }

        @keyframes scrollAnimation {
            from {
                transform: scale(0.5) rotate(0deg);
                background: #007bff;
                opacity: 0.5;
            }
            to {
                transform: scale(1) rotate(360deg);
                background: #28a745;
                opacity: 1;
            }
        }

        .scroll-instruction {
            position: sticky;
            top: 0;
            background: rgba(255, 255, 255, 0.9);
            padding: 10px;
            border-radius: 8px;
            text-align: center;
            font-weight: bold;
            color: #007bff;
            z-index: 10;
        }

        /* ====== PRACTICAL: HOVER-PAUSE CARD ====== */
        .hover-pause-container {
            display: flex;
            gap: 30px;
            justify-content: center;
            flex-wrap: wrap;
            margin: 20px 0;
        }

        .hover-pause-card {
            width: 180px;
            height: 180px;
            background: linear-gradient(135deg, #007bff, #6c5ce7);
            border-radius: 16px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            color: white;
            text-align: center;
            padding: 20px;
            cursor: pointer;
            animation: floatCard 3s ease-in-out infinite;
            box-shadow: 0 8px 25px rgba(0, 123, 255, 0.2);
        }

        .hover-pause-card:hover {
            animation-play-state: paused;
        }

        .hover-pause-card .icon {
            font-size: 2.5rem;
            margin-bottom: 10px;
        }

        .hover-pause-card h4 {
            margin: 0;
            font-size: 1rem;
        }

        .hover-pause-card p {
            margin: 5px 0 0 0;
            font-size: 0.8rem;
            opacity: 0.9;
        }

        @keyframes floatCard {
            0%, 100% { transform: translateY(0); }
            50%      { transform: translateY(-15px); }
        }

        /* ====== PRACTICAL: SCROLL PROGRESS ====== */
        .progress-scroll-container {
            height: 250px;
            overflow-y: scroll;
            background: #f8f9fa;
            border: 2px solid #ddd;
            border-radius: 8px;
            margin: 15px 0;
            position: relative;
        }

        .progress-scroll-content {
            height: 1000px;
            padding: 20px;
        }

        .progress-indicator {
            position: sticky;
            top: 0;
            height: 8px;
            background: #e9ecef;
            border-radius: 4px;
            z-index: 10;
        }

        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #007bff, #6c5ce7);
            border-radius: 4px;
            animation-name: progressGrow;
            animation-duration: 1s;
            animation-timeline: scroll(nearest);
            animation-fill-mode: both;
            transform-origin: left;
        }

        @keyframes progressGrow {
            from { transform: scaleX(0); }
            to   { transform: scaleX(1); }
        }

        /* ====== TIPS ====== */
        .tip-box {
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            border-left: 4px solid #007bff;
            background: #f8f9fa;
        }

        .tip-box.success { border-left-color: #28a745; }
        .tip-box.warning { border-left-color: #ffc107; }
        .tip-box.danger { border-left-color: #dc3545; }
    </style>
</head>
<body>

    <h1>Animation Properties — Part 2</h1>

    <!-- ====== 1. BASIC ANIMATION WITH FILL-MODE ====== -->
    <section>
        <h2>1. animation-fill-mode: forwards</h2>
        <p>The box stays at its final position and color after the animation ends.</p>

        <div class="demo-area">
            <div class="box"></div>
        </div>

        <div class="code-block">
            .box {
                animation-name: exampleAnimation;
                animation-duration: 2s;
                animation-fill-mode: forwards;
            }

            @keyframes exampleAnimation {
                from { transform: translateX(0); background-color: blue; }
                to   { transform: translateX(200px); background-color: red; }
            }
        </div>

        <p class="note">Without <code>fill-mode: forwards</code>, the box would snap back to its original position and color.</p>
    </section>

    <!-- ====== 2. FILL MODE COMPARISON ====== -->
    <section>
        <h2>2. animation-fill-mode Comparison</h2>
        <p>Refresh the page to see the four fill-mode values in action.</p>

        <div class="demo-area" style="flex-direction: column; gap: 20px; align-items: flex-start;">
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 150px;">none:</span>
                <div class="fill-box fill-none">none</div>
            </div>
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 150px;">forwards:</span>
                <div class="fill-box fill-forwards">forwards</div>
            </div>
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 150px;">backwards:</span>
                <div class="fill-box fill-backwards">backwards</div>
            </div>
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 150px;">both:</span>
                <div class="fill-box fill-both">both</div>
            </div>
        </div>

        <div class="code-block">
            .fill-none      { animation-fill-mode: none; }
            .fill-forwards  { animation-fill-mode: forwards; }
            .fill-backwards { animation-fill-mode: backwards; }
            .fill-both      { animation-fill-mode: both; }

            @keyframes fillDemo {
                from { opacity: 0; transform: translateX(-50px); background: #dc3545; }
                to   { opacity: 1; transform: translateX(0); background: #28a745; }
            }
        </div>

        <p class="note"><strong>none</strong> reverts after the animation. <strong>forwards</strong> keeps the end state. <strong>backwards</strong> applies the start state during the delay. <strong>both</strong> does both.</p>
    </section>

    <!-- ====== 3. ANIMATION-PLAY-STATE ====== -->
    <section>
        <h2>3. animation-play-state</h2>
        <p>Hover over the box to pause the animation.</p>

        <div class="demo-area">
            <div class="play-container">
                <div class="play-box">Hover to Pause</div>
            </div>
        </div>

        <div class="code-block">
            .play-box {
                animation: pulseAnim 1.5s ease-in-out infinite;
            }

            .play-box:hover {
                animation-play-state: paused;
            }
        </div>
    </section>

    <!-- ====== 4. ANIMATION-COMPOSITION ====== -->
    <section>
        <h2>4. animation-composition</h2>
        <p>Compare how multiple animations combine with different composition values.</p>

        <div class="demo-area" style="flex-direction: column; gap: 25px; align-items: flex-start;">
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 180px;">replace (default):</span>
                <div class="composition-box comp-replace">replace</div>
            </div>
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 180px;">add:</span>
                <div class="composition-box comp-add">add</div>
            </div>
            <div style="display: flex; align-items: center; gap: 20px;">
                <span style="font-weight: bold; min-width: 180px;">accumulate:</span>
                <div class="composition-box comp-accumulate">accumulate</div>
            </div>
        </div>

        <div class="code-block">
            /* replace: later animation overwrites earlier */
            .comp-replace {
                animation-name: moveRight, rotateBox;
                animation-composition: replace;
            }

            /* add: effects combine (move right AND up) */
            .comp-add {
                animation-name: moveRight, moveUp;
                animation-composition: add;
            }

            /* accumulate: scale values add together */
            .comp-accumulate {
                animation-name: scaleUp, scaleDown;
                animation-composition: accumulate;
            }
        </div>

        <p class="note">With <code>add</code>, the box moves both right and up. With <code>replace</code>, only the second animation's transform applies.</p>
    </section>

    <!-- ====== 5. ANIMATION-TIMELINE (SCROLL) ====== -->
    <section>
        <h2>5. animation-timeline: scroll()</h2>
        <p>Scroll inside the box to see the animation progress with the scroll position.</p>

        <div class="scroll-container">
            <div class="scroll-content">
                <div class="scroll-instruction">⬇️ Scroll down to animate ⬇️</div>
                <div class="scroll-animated-box"></div>
                <p style="margin-top: 20px; color: #6c757d;">
                    The box grows, rotates, and changes color as you scroll.
                    Keep scrolling to see the full animation.
                </p>
                <p style="margin-top: 200px; color: #6c757d;">
                    The animation is driven by the scroll position — not by time.
                </p>
                <p style="margin-top: 200px; color: #6c757d;">
                    Near the bottom — the animation is almost complete.
                </p>
                <p style="margin-top: 200px; color: #6c757d;">
                    🎉 You've reached the end!
                </p>
            </div>
        </div>

        <div class="code-block">
            .scroll-animated-box {
                animation-name: scrollAnimation;
                animation-duration: 1s;
                animation-timeline: scroll(nearest);
                animation-fill-mode: both;
            }

            @keyframes scrollAnimation {
                from {
                    transform: scale(0.5) rotate(0deg);
                    background: #007bff;
                    opacity: 0.5;
                }
                to {
                    transform: scale(1) rotate(360deg);
                    background: #28a745;
                    opacity: 1;
                }
            }
        </div>

        <div class="tip-box warning">
            <strong>⚠️ Browser Support:</strong>
            <code>animation-timeline</code> is a modern feature. Check <a href="https://caniuse.com/css-scroll-timeline" target="_blank">caniuse.com</a> for current browser support.
        </div>
    </section>

    <!-- ====== 6. PRACTICAL: HOVER-PAUSE CARDS ====== -->
    <section>
        <h2>6. Practical Example: Hover-Pause Cards</h2>
        <p>Hover over a card to pause its floating animation.</p>

        <div class="hover-pause-container">
            <div class="hover-pause-card">
                <div class="icon">🚀</div>
                <h4>Fast</h4>
                <p>Hover to pause</p>
            </div>
            <div class="hover-pause-card">
                <div class="icon">🎨</div>
                <h4>Beautiful</h4>
                <p>Hover to pause</p>
            </div>
            <div class="hover-pause-card">
                <div class="icon">🔒</div>
                <h4>Secure</h4>
                <p>Hover to pause</p>
            </div>
        </div>

        <div class="code-block">
            .hover-pause-card {
                animation: floatCard 3s ease-in-out infinite;
            }

            .hover-pause-card:hover {
                animation-play-state: paused;
            }

            @keyframes floatCard {
                0%, 100% { transform: translateY(0); }
                50%      { transform: translateY(-15px); }
            }
        </div>
    </section>

    <!-- ====== 7. PRACTICAL: SCROLL PROGRESS ====== -->
    <section>
        <h2>7. Practical Example: Scroll Progress Bar</h2>
        <p>Scroll inside the box to see the progress bar fill based on scroll position.</p>

        <div class="progress-scroll-container">
            <div class="progress-scroll-content">
                <div class="progress-indicator">
                    <div class="progress-fill"></div>
                </div>
                <p style="margin-top: 20px; color: #6c757d;">
                    The progress bar at the top fills as you scroll.
                </p>
                <p style="margin-top: 300px; color: #6c757d;">
                    Keep scrolling to see the progress grow.
                </p>
                <p style="margin-top: 300px; color: #6c757d;">
                    Almost there...
                </p>
                <p style="margin-top: 300px; color: #6c757d;">
                    🎉 Complete!
                </p>
            </div>
        </div>

        <div class="code-block">
            .progress-fill {
                animation-name: progressGrow;
                animation-duration: 1s;
                animation-timeline: scroll(nearest);
                animation-fill-mode: both;
                transform-origin: left;
            }

            @keyframes progressGrow {
                from { transform: scaleX(0); }
                to   { transform: scaleX(1); }
            }
        </div>
    </section>

    <!-- ====== 8. REFERENCE TABLES ====== -->
    <section>
        <h2>8. Reference Tables</h2>

        <h3>Animation Properties — Part 2</h3>
        <table class="reference-table">
            <tr>
                <th>Property</th>
                <th>Description</th>
                <th>Default</th>
            </tr>
            <tr>
                <td><code>animation-fill-mode</code></td>
                <td>Styles before/after animation</td>
                <td><code>none</code></td>
            </tr>
            <tr>
                <td><code>animation-play-state</code></td>
                <td>Running or paused</td>
                <td><code>running</code></td>
            </tr>
            <tr>
                <td><code>animation-timeline</code></td>
                <td>Timeline that controls the animation</td>
                <td><code>auto</code></td>
            </tr>
            <tr>
                <td><code>animation-composition</code></td>
                <td>How multiple animations combine</td>
                <td><code>replace</code></td>
            </tr>
        </table>

        <h3>animation-fill-mode Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>none</code></td>
                <td>No styles applied outside the animation (default)</td>
            </tr>
            <tr>
                <td><code>forwards</code></td>
                <td>Keep the last keyframe's styles</td>
            </tr>
            <tr>
                <td><code>backwards</code></td>
                <td>Apply the first keyframe's styles during delay</td>
            </tr>
            <tr>
                <td><code>both</code></td>
                <td>Apply both forwards and backwards</td>
            </tr>
        </table>

        <h3>animation-play-state Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>running</code></td>
                <td>Animation is playing (default)</td>
            </tr>
            <tr>
                <td><code>paused</code></td>
                <td>Animation is paused</td>
            </tr>
        </table>

        <h3>animation-timeline Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>auto</code></td>
                <td>Default document timeline (default)</td>
            </tr>
            <tr>
                <td><code>none</code></td>
                <td>No timeline — animation doesn't play</td>
            </tr>
            <tr>
                <td><code>scroll()</code></td>
                <td>Binds animation to scroll position</td>
            </tr>
            <tr>
                <td><code>view()</code></td>
                <td>Binds animation to element visibility in viewport</td>
            </tr>
        </table>

        <h3>animation-composition Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>replace</code></td>
                <td>New animations overwrite previous ones (default)</td>
            </tr>
            <tr>
                <td><code>add</code></td>
                <td>New animations add to existing effects</td>
            </tr>
            <tr>
                <td><code>accumulate</code></td>
                <td>Similar to add, with additional rules for keyframes</td>
            </tr>
        </table>
    </section>

    <!-- ====== 9. BEST PRACTICES ====== -->
    <section>
        <h2>9. Best Practices</h2>

        <div style="background: #d4edda; padding: 20px; border-radius: 8px; border-left: 4px solid #28a745;">
            <h3 style="margin-top: 0; color: #155724;">✅ Do This:</h3>
            <ul>
                <li>Use <code>animation-fill-mode: forwards</code> to keep the end state</li>
                <li>Use <code>animation-fill-mode: backwards</code> to apply start styles during delay</li>
                <li>Use <code>animation-play-state: paused</code> on hover for interactive control</li>
                <li>Use <code>animation-timeline: scroll()</code> for scroll-driven animations</li>
                <li>Use <code>animation-composition: add</code> to combine transform effects</li>
                <li>Always provide fallbacks for modern features</li>
                <li>Respect <code>prefers-reduced-motion</code> for accessibility</li>
            </ul>
        </div>

        <div style="background: #f8d7da; padding: 20px; border-radius: 8px; border-left: 4px solid #dc3545; margin-top: 15px;">
            <h3 style="margin-top: 0; color: #721c24;">❌ Don't Do This:</h3>
            <ul>
                <li>Don't forget <code>animation-fill-mode</code> — elements snap back without it</li>
                <li>Don't use <code>animation-timeline</code> without a fallback for unsupported browsers</li>
                <li>Don't confuse <code>animation-composition: add</code> with <code>replace</code></li>
                <li>Don't pause animations that are essential for feedback</li>
                <li>Don't forget that scroll-driven animations need a scrollable container</li>
                <li>Don't forget about <code>prefers-reduced-motion</code></li>
            </ul>
        </div>

        <div class="code-block">
            /* Accessibility: respect reduced motion */
            @media (prefers-reduced-motion: reduce) {
                .box,
                .fill-box,
                .play-box,
                .composition-box,
                .scroll-animated-box,
                .hover-pause-card {
                    animation: none;
                }
            }

            /* Scroll-driven animation with fallback */
            .scroll-animated-box {
                animation-name: scrollAnimation;
                animation-duration: 1s;
                animation-fill-mode: both;
            }

            @supports (animation-timeline: scroll()) {
                .scroll-animated-box {
                    animation-timeline: scroll(nearest);
                }
            }
        </div>
    </section>

</body>
</html>

Quick Reference

PropertyDescriptionDefault
animation-fill-modeStyles before/after animationnone
animation-play-stateRunning or pausedrunning
animation-timelineTimeline that controls the animationauto
animation-compositionHow multiple animations combinereplace

animation-fill-mode Values

ValueDescription
noneNo styles applied outside the animation (default)
forwardsKeep the last keyframe’s styles
backwardsApply the first keyframe’s styles during delay
bothApply both forwards and backwards

animation-play-state Values

ValueDescription
runningAnimation is playing (default)
pausedAnimation is paused

animation-timeline Values

ValueDescription
autoDefault document timeline (default)
noneNo timeline — animation doesn’t play
scroll()Binds animation to scroll position
view()Binds animation to element visibility

animation-composition Values

ValueDescription
replaceNew animations overwrite previous ones (default)
addNew animations add to existing effects
accumulateSimilar to add, with additional rules for keyframes

Best Practices

Do This:

/* Keep the end state */
.box {
    animation-fill-mode: forwards;
}

/* Pause on hover */
.box:hover {
    animation-play-state: paused;
}

/* Scroll-driven animation with fallback */
@supports (animation-timeline: scroll()) {
    .box {
        animation-timeline: scroll();
    }
}

/* Combine transforms */
.box {
    animation-name: moveRight, rotate;
    animation-composition: add;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
    .box {
        animation: none;
    }
}

Don’t Do This:

/* Don't forget fill-mode */
.box {
    animation: slideIn 1s;
    /* Snaps back after animation */
}

/* Don't use animation-timeline without fallback */
.box {
    animation-timeline: scroll();
    /* Won't work in unsupported browsers */
}

/* Don't use replace when you want combined effects */
.box {
    animation-name: moveRight, moveUp;
    animation-composition: replace;
    /* Only moveUp applies — moveRight is overwritten */
}

Pro Tip: animation-fill-mode: forwards is essential for entrance animations — without it, elements snap back to their original state. animation-play-state: paused on hover gives users control over animations, which is great for accessibility. animation-timeline: scroll() enables scroll-driven animations — a modern, performant way to create engaging scroll experiences (always provide a fallback). animation-composition: add lets you combine multiple transforms (e.g., move right AND rotate). And always respect prefers-reduced-motion — some users experience discomfort from animations!


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!