Controlling Animation Fill Modes with `animation-fill-mode

In modern web development, CSS animations have become a crucial method for enhancing user experience and interface interactivity. However, many developers, when defining animations using @keyframes, focus primarily on the start and end states while overlooking the visual continuity of elements before and after the animation. In such cases, the animation-fill-mode property becomes particularly important. It allows us to control whether CSS animations maintain their initial or final states before they begin and after they end, thereby solving issues like elements "instantly disappearing" or "suddenly appearing," and achieving smoother visual transitions.

Default Behavior and Common Pain Points

Without an explicit animation-fill-mode setting, CSS animations follow specific default rules: before the animation starts, the element remains in its normal, non-animated state; after the animation ends, the element immediately reverts to its initial state, as if the animation never occurred. While this mechanism might be acceptable for simple displacements or color changes, it often results in unnatural visual jumps in complex layout adjustments or state switching scenarios.

For example, when a button transitions from an "inactive" state to an "active" state, if the animation immediately resets back to the inactive style upon completion, users may perceive a sense of "teleportation" because the element undergoes a drastic state change at the exact moment the animation ends. To address this pain point, we need to leverage animation-fill-mode to force the retention of the visual result during the animation process.

Controlling Pre-Animation Fill Mode: forwards and backwards

The animation-fill-mode property primarily includes four possible values: none (default), forwards, backwards, and both. Among these, control over the "before start" behavior is mainly reflected in the backwards and forwards values.

When you wish for an element to present its final state before the animation begins, or to maintain a certain intermediate state during the animation process, you can use backwards. This value indicates that before the animation starts, the element will immediately apply the state of its initial frame. This is very useful in certain special entrance animations, where content is displayed first, followed by the playback of the animation process, rather than waiting for the animation to fully complete before displaying.

/* Example: Using backwards to show the element at its final position before the animation starts */
@keyframes slideIn {
  from {
    transform: translateX(100%); /* Initial state off-screen */
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.button-slide {
  animation-name: slideIn;
  animation-duration: 1s;
  animation-fill-mode: backwards; /* Element is in the final state before animation starts */
}

In the code above, when .button-slide is added to the DOM, it immediately appears at the center of the screen (i.e., the translateX(0) position), and only then does the animation process from right to left begin. This "display first, then move" effect can significantly reduce user waiting anxiety in certain UI designs.

Controlling Post-Animation Fill Mode: forwards and both

Besides the state before the animation, the behavior after the animation ends is equally critical. By default, elements immediately revert to their initial state after the animation finishes. To retain the final form at the end of the animation, the forwards value must be used.

forwards indicates that after the animation ends, the element will maintain its last frame (i.e., the to state) unchanged. This is crucial for scenarios requiring persistent display of results. For example, when a loading progress bar completes its load, we want it to remain in the full-bar state and continue displaying, rather than immediately jumping back to an empty shell upon completion.

/* Example: Using forwards to keep the element in its final state after the animation ends */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.content-reveal {
  animation-name: fadeIn;
  animation-duration: 0.5s;
  animation-fill-mode: forwards; /* Element remains visible after animation ends */
}

By setting animation-fill-mode: forwards, .content-reveal will permanently stay in the fully opaque state (i.e., opacity: 1) after the 0.5-second fade-in animation completes, until a new animation instruction overrides it. This mechanism greatly simplifies subsequent state management logic, avoiding extensive timer cleanup work in JavaScript.

Combining Usage: Achieving Perfect Visual Transitions

In practical development, it is often necessary to control both the start and end behaviors of an animation simultaneously. In such cases, we can combine backwards and forwards, or directly use the both value to solve both issues at once. both is a comprehensive version of the above two modes; it indicates applying the initial frame state before the animation starts and maintaining the final frame state after the animation ends.

This combination mode is commonly used in layout adjustment scenarios requiring smooth transitions. For example, when a list item transitions from "hidden" to "expanded," we want it to occupy space before expanding (to avoid content jitter) and maintain the expanded state after expansion.

@keyframes expandItem {
  from {
    max-height: 0;
    overflow: hidden;
  }
  to {
    max-height: 200px; /* Assuming content height */
    overflow: visible;
  }
}

.expandable-list-item {
  animation-name: expandItem;
  animation-duration: 0.3s;
  animation-fill-mode: both; /* Maintain initial state before start, final state after end */
}

In this example, when .expandable-list-item is triggered, it immediately occupies max-height: 200px of space (preventing sudden content pop-ups that cause page jumps) and continues to maintain this height after the animation playback completes. This not only enhances the visual experience but also reduces scrolling bar jitter caused by layout mutations.

Summary and Best Practices

Mastering animation-fill-mode is one of the necessary conditions for writing high-quality CSS animations. It grants developers finer control over the animation lifecycle, enabling web interfaces to present more natural and cohesive interactive effects. In practical applications, it is recommended to follow these principles:

  1. Clarify Intent: Think through the visual state requirements before and after the animation during the design phase, avoiding reliance on the default "reset" behavior.
  2. Pair with transition: For non-critical path state changes, consider combining the transition property to achieve finer-grained smooth transitions, while animation-fill-mode is more suitable for independent, periodic, or state-persistent animation scenarios.
  3. Performance Considerations: While animation-fill-mode does not change the number of render frames, maintaining the final state in frequently triggered complex animations may increase DOM computation burden; usage scenarios should be weighed accordingly.

By reasonably using animation-fill-mode, developers can reduce over-reliance on JavaScript state management and build efficient and aesthetically pleasing interactive experiences using pure CSS.