카테고리 더 보기가 접혀있을 때는 최소 아이템 4개를 보여주고, 더보기를 눌렀을 때 있는 아이템 갯수만큼 4개씩 배치된 리스트가 출력되는 형식의 모습이다. 데이터가 없는 상태라고 가정했을 때 하드코딩으로 리스트를 데이터에 넣어서 구현을 해봤다.
setExpand() {
this.expandStatus = !this.expandStatus
const refHeight = this.$refs.prdtList.clientHeight + 24
if (!this.expandStatus) {
this.foldStyle.top = '106px'
} else {
this.foldStyle.top = `${refHeight}px`
}
},
clientHeight로 리스트의 높이값과 + 기본적으로 열려있는 값을 refHeight 변수에 담았다.
클릭을 눌렀을 때 (카테고리 리스트가 확장이 됐을 경우): 추가된 리스트는 8개라고 가정했을 때의 높이값을 담았다.
그리고 스타일과 클래스를 바인딩 시켜서 이벤트에서 만들어진 값들을 적용시키게 구현을 했다.
문제점
1. 동적으로 받아오는 데이터의 개수가 달라지면?
- 더보기의 리스트가 현재는 8개 기준으로 높이값을 고정한 상태로 스타일을 적용해줬기 때문에 8개 이상의 리스트가 들어오게 되면 그 이상의 아이템은 보여줄 수 없다. 4개이하의 리스트만 가져오게 된다면, 높이값이 맞아지지 않게 된다.
2. 성능적인 측면
리스트 전체의 높이값을 가져오고 폴더가 넓혀지고 줄여질 때 Height값을 변경하게 했다. DOM 노드 크기를 변경하기 때문에 클릭할 때마다 해당 화면을 다시 계산하고 그리게 되어 불필요한 성능야기를 유발시키게 되버린다.
<template>
<div class="home-wrap" :class="componentData.currentTab">
<!-- categories -->
<div class="categories" :class="{ moreList: expandStatus }">
<div ref="prdtList" class="product-list">
<li v-for="(prdt, itemIdx) in categories" :key="`category_${itemIdx}`">
<button type="button" class="list-wrap">
<div v-lazy:background-image="prdt.itemImgFileUrl" class="thumbnail is-size-xsmall">
<i v-if="!prdt.itemImgFileUrl" class="no-img"></i>
</div>
<p class="tit">{{ prdt.tit }}</p>
</button>
</li>
</div>
<button
v-if="categories && categories.length > 4"
type="button"
class="more"
:class="{ fold: expandStatus }"
:style="foldStyle"
@click="setExpand"
>
<p class="arrow">카테고리 더보기</p>
</button>
</div>
</div>
</template>
<script>
export default {
name: 'Categories',
props: {
componentData: {
type: Object,
default: () => {},
},
},
data() {
return {
expandStatus: false, //더보기
categories: [
{
tit: '크리스마스',
},
{
tit: '생일/축하',
},
{
tit: '응원/감사',
},
{
tit: 'MD상품',
},
{
tit: '크리스마스',
itemImgFileUrl: 'https://~~~~~~~~~~5 ,
},
{
tit: '생일/축하',
itemImgFileUrl: 'https://~~~~~~~~~~5',
},
{
tit: '응원/감사',
itemImgFileUrl: 'https://~~~~~~~~~~.com ,
},
{
tit: 'MD상품',
},
],
foldStyle: {
top: '106px',
},
}
},
watch: {},
mounted() {},
methods: {
setExpand() {
this.expandStatus = !this.expandStatus
const refHeight = this.$refs.prdtList.clientHeight + 24
if (!this.expandStatus) {
this.foldStyle.top = '106px'
} else {
this.foldStyle.top = `${refHeight}px`
}
},
},
}
</script>
<style lang="scss" scoped>
.home-wrap {
.categories {
position: relative;
height: auto;
padding: var(--spacing-md) var(--spacing-lg) var(--spacing-sm-02);
box-sizing: border-box;
.product-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
column-gap: 12px;
row-gap: 8px;
li {
height: 82px;
list-style: none;
.list-wrap {
align-items: center;
flex-direction: column;
gap: var(--spacing-xs);
}
}
}
.more {
position: absolute;
left: var(--spacing-sm-02);
width: 100%;
height: 82px;
background: var(--background-primary);
padding: var(--spacing-sm-02) var(--spacing-sm-03);
border-top: 1px solid var(--border-tertiary);
&.fold {
height: 42px;
.arrow:after {
transform: rotate(180deg);
}
}
.arrow {
position: relative;
font-size: var(--fontsize-small);
font-weight: var(--fontweight-medium);
color: var(--surface-default-black);
}
::v-deep .arrow:after {
width: 16px;
height: 16px;
top: 10%;
left: 63%;
transform: rotate(0);
background: url('../../../assets/img/icon/icon_arrow.svg') no-repeat center/contain;
}
}
&.moreList {
margin-bottom: 54px;
}
}
}
</style>
해결방법
vue.js에서 제공하는 Transition api를 사용하기로 했다.
어플리케이션에서 추가, 제거, 업데이트 될 때 애니메이션등 다양한 방법을 제공하는데 css 트렌지션이나 애니메이션을 위한 클래스를 자동적으로 적용하고 javascript를 사용해서 DOM을 직접 조작하는 도구이다.
진입/ 진출 트랜지션에 적용되는 6개의 클래스가 있다. 이름이 지정이 되면 v대신 접두사로 이름을 붙이면 된다.
- v-enter-from: 진입 시작 상태. 엘리먼트가 삽입되기 전에 추가되고, 엘리먼트가 삽입되고 1 프레임 후 제거됩니다.
- v-enter-active: 진입 활성 상태. 모든 진입 상태에 적용됩니다. 엘리먼트가 삽입되기 전에 추가되고, 트랜지션/애니메이션이 완료되면 제거됩니다. 이 클래스는 진입 트랜지션에 대한 지속 시간, 딜레이 및 이징(easing) 곡선을 정의하는 데 사용할 수 있습니다.
- v-enter-to: 진입 종료 상태. 엘리먼트가 삽입된 후 1 프레임 후 추가되고(동시에 v-enter-from이 제거됨), 트랜지션/애니메이션이 완료되면 제거됩니다.
- v-leave-from: 진출 시작 상태. 진출 트랜지션이 트리거되면 즉시 추가되고 1 프레임 후 제거됩니다.
- v-leave-active: 진출 활성 상태. 모든 진출 상태에 적용됩니다. 진출 트랜지션이 트리거되면 즉시 추가되고, 트랜지션/애니메이션이 완료되면 제거됩니다. 이 클래스는 진출 트랜지션에 대한 지속 시간, 딜레이 및 이징 곡선을 정의하는 데 사용할 수 있습니다.
- v-leave-to: 진출 종료 상태. 진출 트랜지션이 트리거된 후 1 프레임이 추가되고(동시에 v-leave-from이 제거됨), 트랜지션/애니메이션이 완료되면 제거됩니다.
변경한 내 작업 본은 커스텀된 트랜지션 클래스를 사용했는데 props에 트랜지션 클래스를 정의해서 전달을 한다.
이로써 문제점이였던 리스트의 갯수가 변경될 때 마다의 높이를 조절할 수 있게되고 transform을 사용해서 성능저하를 방지했다.
자세한 방법은 공식 문서에 나와 있다.
https://ko.vuejs.org/guide/built-ins/transition
[변경 적용 후]
<Transition
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight">
<div class="product-list">
<ul v-for="(prdt, itemIdx) in categories" :key="`category_${itemIdx}`">
<li v-if="itemIdx < 4">
<button type="button" class="list-wrap">
<div v-lazy:background-image="prdt.itemImgFileUrl" class="thumbnail is-size-xsmall">
<i v-if="!prdt.itemImgFileUrl" class="no-img" :class="`no-img${itemIdx + 1}`"></i>
</div>
<p class="tit">{{ prdt.tit }}</p>
</button>
</li>
<li v-if="expandStatus && itemIdx > 3">
<button type="button" class="list-wrap">
<div v-lazy:background-image="prdt.itemImgFileUrl" class="thumbnail is-size-xsmall">
<i v-if="!prdt.itemImgFileUrl" class="no-img" :class="`no-img${itemIdx + 1}`"></i>
</div>
<p class="tit">{{ prdt.tit }}</p>
</button>
</li>
</ul>
</div>
</Transition>
<button v-if="categories && categories.length > 4" type="button" class="more" :class="{ open: expandStatus }" @click="setExpand">
<p class="arrow">카테고리 더보기</p>
</button>
...
methods: {
setExpand() {
this.expandStatus = !this.expandStatus
},
},
댓글