Search code examples
cssanimationvuejs3accordionnuxtjs3

Animate accordion panels when hide and show a panel


I have created an Accordion component in a Vue3 and Nuxt3 project. everything is good except that I can't put an animation on the height of accordion panels. I want a smooth remove when I hide a panel from this accordion.

here are the accordion panels:

const panels = [
    {id:1,title:'Test Accordion 1',icon:'',},
    {id:2,title:'Test Accordion 2',icon:'',},
    {id:3,title:'Test Accordion 3',icon:'',},
]

and where I use that component like:

<accordion :panels="panels">

    <template #1>
         this is one
    </template>
    <template #2>
        this is two
    </template>
    <template #3>
        this is three
    </template>

</accordion>

and this is the SFC file:

<script setup lang="ts">

const props = defineProps({
    panels: {type: Array,},
})

const current = ref(1)

function toggleAccardion(index: any) {
    if (current.value == index) {
        current.value = 0
        return
    }
    current.value = index
}
</script>
<template>

    <div v-for="panel in panels" :key="panel.id">

        <div class="d-flex flex-row p-4 cursor-pointer bg-gray-200" @click.prevent="toggleAccardion(panel.id)">
            <div v-if="panel.icon">
                {{ panel.icon }}
            </div>
            <div class="flex-grow-1">
                <h4 class="mb-0">{{ panel.title }}</h4>
            </div>
            <div>
                <i v-if="panel.id  === current" class="ki-outline ki-minus fs-4 text-gray-800"></i>
                <i v-else class="ki-outline ki-plus fs-4 text-gray-800"></i>
            </div>
        </div>

        <div class="p-4" v-if="panel.id  === current">
            <slot :name="panel.id"></slot>
        </div>

    </div>

</template>


Solution

  • You can use <transition> component which works as expected in this case. Look here for more info.

    First of all, wrap your content (panel) with <Transition> component like this:

    <Transition>
        <div class="p-4" v-if="panel.id  === current">
            <slot :name="panel.id"></slot>
        </div>
    </Transition>
    

    Set a name attribute for transition component: <Transition name="accordion">. Now You can create custom animation with exact same name as name of Transition.

    @keyframes accordion {
        0% {
            max-height: 0;
        }
    
        100% {
            max-height: 200px; // max possible value
        }
    }
    
    .accordion-enter-active {
        animation: accordion 0.2s linear;
    }
    
    .accordion-leave-active {
        animation: accordion 0.2s linear reverse;
    }
    

    Set max possible height of panel for max-height, because we can't animate with height:auto or percent value

    Well, if You try this, it looks glitchy. Because there is also some padding for that panel. So we should add padding to that animation. Update @keyframe as following:

    @keyframes accordion {
        0% {
            max-height: 0;
            padding: 0 16px; // horizontal padding should not be animated
        }
    
        100% {
            max-height: 200px;
            padding: 16px;
        }
    }
    

    The more accurate the max-height, the smoother the animation.