Search code examples
angularcss-animations

How to get smooth animation with contents using 'gap' in Angular?


I've been using the following animation for a while and it works fine to expand / collapse an element:

const expanded = style({ height: '*', opacity: 1 });
const collapsed = style({ height: 0, opacity: 0 });
const animation = animate(`300ms cubic-bezier(0.4, 0.0, 0.2, 1)`);

export const expandCollapse = trigger('expandCollapse', [
  transition(':enter', [collapsed, animation, expanded]),
  transition(':leave', [animation, collapsed]),
]);

And this works great to animate an element when it is displayed or hidden via *ngIf:

<div *ngIf="isExpanded" @expandCollapse>
  <!-- Content Here -->
</div>

Apparently, I've never used this with contents that uses a gap, because when I do the animation "snaps" at the beginning and the end. I thought I could solve this by also animating the gap property, but no luck:

const expanded = style({ height: '*', gap: '*', opacity: 1 });
const collapsed = style({ height: 0, gap: 0, opacity: 0 });

How can I smooth this out?

Here is a StackBlitz that demonstrates the current behavior.


Solution

  • The issue you are facing is because CSS grid or flex gap changes are not automatically animatable with basic CSS or Angular animations. The gap property isn't directly supported for smooth transactions in many browsers, leading to the snapping effect.

    To fix this, you can use a workaround where you explicitly animate the gap using margin or padding properties instead, which are fully animatble.

    1. Remove the gap property from the animation styles and instead animate margins or paddings of the inner child elements.
    2. Add wrapper elements around the content to control margins or paddings.

    Let me give you the updated code.

    Modify the Styles:

    const expanded = style({ height: '*', opacity: 1, marginTop: '*', marginBottom: '*' });
    const collapsed = style({ height: 0, opacity: 0, marginTop: 0, marginBottom: 0 });
    const animation = animate(`300ms cubic-bezier(0.4, 0.0, 0.2, 1)`);
    
    export const expandCollapse = trigger('expandCollapse', [
      transition(':enter', [collapsed, animation, expanded]),
      transition(':leave', [animation, collapsed]),
    ]);
    

    Update the HTML:

    <div *ngIf="isExpanded" @expandCollapse>
      <div class="content-wrapper">
        <!-- Content Here -->
      </div>
    </div>
    

    Add CSS(optional)

    .content-wrapper {
      display: flex; /* or grid, if needed */
      /* other styling */
    }