Vue Expand/Collapse Animation for FAQ Section
The article shows how to create a smooth FAQ expand‑collapse effect in Vue by storing each item’s visibility in a show flag, wrapping the answer in a transition component, and using JavaScript hooks to set explicit heights and overflow while rotating the arrow icon for visual feedback.
In many mobile apps a FAQ (question‑answer) module is required. The static page is simple, but the expand/collapse effect for the answer part can be tricky because the answer container’s height is determined by its inner content (height: auto), which makes CSS height transitions ineffective.
The solution is demonstrated with Vue. First, the data structure is defined (using TypeScript syntax for clarity):
interface QaItem {
Q: string; // question
A: string; // answer
show: boolean; // whether the answer is displayed
}
type QaList = QaItem[];The corresponding Vue template (simplified) looks like this:
<div class="qa panel">
<div class="qa__title">Common Questions</div>
<div class="list-qa">
<div v-for="(item, ind) in qaList" :key="ind" class="list-qa__item">
<div class="list-qa__question">
<span>{{ item.Q }}</span>
<span class="list-qa__question__arrow" />
</div>
<span class="list-qa__answer" v-show="item.show">{{ item.A }}</span>
</div>
</div>
</div>The answer visibility is controlled by the show property and rendered with v-show . For the expand/collapse animation, Vue’s transition component is used together with JavaScript hook functions that explicitly set the element’s height.
<transition
name="slide"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
>
<span v-show="item.show" class="list-qa__answer">{{ item.A }}</span>
</transition>JavaScript hook implementations:
beforeEnter(el) {
el.style.transition = '0.3s height ease-in-out';
el.style.overflow = 'hidden';
},
enter(el) {
el.style.height = 'auto';
const endHeight = window.getComputedStyle(el).height;
el.style.height = '0px';
el.offsetHeight; // force reflow
el.style.height = endHeight;
},
afterEnter(el) {
el.style.transition = '';
el.style.overflow = 'visible';
},
beforeLeave(el) {
el.style.transition = '0.3s height ease-in-out';
el.style.overflow = 'hidden';
},
leave(el) {
el.style.height = window.getComputedStyle(el).height;
el.offsetHeight; // force reflow
el.style.height = '0px';
},
afterLeave(el) {
el.style.transition = '';
el.style.overflow = 'visible';
}The arrow rotation effect is achieved with a simple CSS transition. When the arrow is clicked, a class is toggled to rotate the icon 180 degrees:
<span class="list-qa__question__arrow"
:class="{'list-qa__question__rotate-arrow': !item.show}"
@click="onClickProblem(ind)" /> onClickProblem(index) {
const qaItem = this.qaList[index];
this.$set(qaItem, 'show', !qaItem.show);
}Corresponding CSS:
.list-qa__question__arrow {
width: 12px;
height: 12px;
background: url(https://m.hellobike.com/resource/helloyun/21588/RIBiB_SketchPngf4c3c2445f4522fe182c1d02d45a6201fa03ecfb14550a3269204012abdcfa09) center no-repeat;
transition: transform .4s;
}
.list-qa__question__rotate-arrow {
transform: rotateZ(180deg);
transition: transform .4s;
}By explicitly setting the element’s height from 0 to the computed pixel value (and vice‑versa) and using overflow: hidden , the desired smooth expand/collapse animation is achieved, while the arrow rotation provides a visual cue for the user.
This tutorial demonstrates the principle of CSS transitions, the limitation of transitioning to/from auto , and a practical Vue‑based workaround.
HelloTech
Official Hello technology account, sharing tech insights and developments.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.