Vue.js 높이 자동 확인 boiler code

강정우·2023년 5월 27일
0

vue.js

목록 보기
61/72
post-custom-banner

  • 위 코드를 작성하기위한 핵심 개념에 대해 설명하겠다.

1. html 구조 잡기

  • 우선적으로 나타날 요소와 해당 ref, ref에 연결할 메서드들을 구축한다.
<section v-for="post in posts" :key="post">
  <div>
    <ul @click="[loadDetail(post.postSeq), toggleAccordion(post.postSeq)]">
      	...
      <li style="width:220px">{{setDate(post.postTime)}}</li>
    </ul>
  </div>
  <transition name="detail" mode="out-in">
    <div v-if=post.view>
      <div class="txt-textarea">
        <textarea 
                  :ref="getContentRef(post.postSeq)" 
                  disabled="isEdit" 
                  v-model="check_detailData.content" 
                  :style="{height:textAreaHeight+'px'}"
        ></textarea>
        
        ...
  • 여기서 사용되는 코드는 loadDetail이라는 클릭 시 => async로 데이터를 로딩하는 함수이다.
    toggleAccordion은 위 사진처럼 다른 요소를 클릭하면 클릭한 요소를 제외한 나머지 다른 요소는 숨겨주는 함수이다.

  • 마지막으로 해당 testarea에 대한 값을 ref를 v-bind로 이어주고 해당 요소에 값을 적용할 수 있도록 동적 styling을 부여하면 된다.

2. 사용할 변수 선언

const textAreaHeight = ref(0)
const contentRefs = ref([])
  • textarea의 높이값을 설정할 testAreaHeight을 설정하고 contentRefs에는 해당 요소가 순차적으로 들어갈 수 있도록 작성한다.

3. ref 가져오기

  • vue의 transiton에는 before-enter라는 directive가 존재하나 이상하게 나는 적용되지 않았다. 그래서 ref를 이용하여 확실히 해당요소에 대한 style의 높이값을 부여하였다.

  • 이에 우선 되어야할 것은 @click 이벤트 시 현재 대상의 ref를 가져오는 것이 최우선이다.

const contentRefs = ref({null})

const getContentRef = (id) => {
  return (el) => {
    if(el){
      contentRefs[id].value = el;
    } else {
      contentRefs[id].value = null;
    }
  };
}
  • 위 코드는 contentRefs라는 ref를 저장할 객체를 생성해주고, getContentRef로 요소를 순회하며 요소의 key값이 id인 해당 요소를 반환하여 저장한다.

4. 해당 ref의 높이 값 설정하기

  • 역시 다음을 할 일은 ref에 해당하는 요소를 찾았으니 해당 요소의 높이 값을 설정해주는 코드를 작성하면 된다.
const setHeight = (idx) => {
  const contentElement = contentRefs[idx].value;
  if (contentElement) {
    textAreaHeight.value = contentElement.scrollHeight.value;
  }else{
    textAreaHeight.value = 0;
  }
},
  • 이제 위에서 작성한 코드를 async로 실행시켜주면 같은 tick에 돌며 적용이 된다.
async loadDetail(postSeq) {
  textAreaHeight.value = 0;
  try {
    await 상태변경함수||dispatch함수;
    setHeight(postSeq);
  } catch (err) {
    console.log(err.message);
  }
},
  • 여기서 이전의 textAreaHeight값이 적용되는 것을 방지하고자 0으로 초기화 해주고
    await 함수 다음에 setHeight으로 해당 로직이 실행한 후 동작할 수 있도록 한다.

  • 이때 async 할 dispatch함수가 없다면 toggle accordion 같은 상태 변경함수를 await으로 실행시킨 후 작성해도 된다.

5. style 설정하기

.detail-enter-from,
.detail-leave-to {
    opacity: 0;
    height: 0px;
}

.detail-enter-active {
    transition: all 0.2s ease-out;
}

.detail-leave-active {
    transition: all 0.2s ease-in;
}

.detail-enter-to,
.detail-leave-from {
    opacity: 1;
}
  • 여기서의 핵심은 enter-to(leave-from), enter-from(leave-to) 를 신경써줘서 잘 작성만해주고
    enter-to(leave-from)에는 height값을 설정을 안 해주거나 auto로 설정해주는 것이 포인트이다. => 그래야 위 html에서 동적으로 style한 높이 값이 알아서 작용하기 때문이다.

ps. boiler 코드

<template>
  <div>
    <div
      v-for="item in menuItems"
      :key="item.id"
      class="accordion-item"
      :class="{ active: item.isActive }"
    >
      <div class="accordion-header" @click="toggleItem(item)">
        {{ item.title }}
      </div>
      <transition name="accordion-slide">
        <div v-if="item.isActive" class="accordion-content" :style="{ height: item.contentHeight + 'px' }" :ref="getContentRef(item.id)">
          {{ item.content }}
        </div>
      </transition>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      menuItems: [
        {
          id: 1,
          title: '메뉴 1',
          content: '메뉴 1의 내용',
          isActive: false,
          contentHeight: 0
        },
        {
          id: 2,
          title: '메뉴 2',
          content: '메뉴 2의 내용',
          isActive: false,
          contentHeight: 0
        },
        {
          id: 3,
          title: '메뉴 3',
          content: '메뉴 3의 내용',
          isActive: false,
          contentHeight: 0
        }
      ],
      contentRefs: {} // ref를 저장할 객체
    };
  },
  methods: {
    toggleItem(item) {
      item.isActive = !item.isActive;
      this.updateContentHeight(item);
    },
    updateContentHeight(item) {
      const contentElement = this.contentRefs[item.id];
      if (contentElement) {
        item.contentHeight = contentElement.scrollHeight;
      }
    },
    getContentRef(id) {
      return (el) => {
        this.contentRefs[id] = el;
      };
    }
  }
};
</script>

<style>
.accordion-item {
  border: 1px solid #ccc;
  margin-bottom: 10px;
}

.accordion-header {
  cursor: pointer;
  padding: 10px;
}

.accordion-content {
  padding: 10px;
  background-color: #f5f5f5;
  overflow: hidden;
}

.accordion-slide-enter-active,
.accordion-slide-leave-active {
  transition: height 0.3s;
}

.accordion-slide-enter,
.accordion-slide-leave-to {
  height: 0;
  overflow: hidden;
}
</style>

@after-enter

<transition name="detail" @after-enter="setHeight" @after-leave="resetHeight">
  <div v-if="showDetail" class="detailBox">
    <!-- Details go here -->
  </div>
</transition>
  • 여기서 setHeight와 resetHeight 메서드를 Vue 인스턴스에 추가해야 한다.
    setHeight 메서드는 요소의 높이 값을 자동으로 설정하고 resetHeight 메서드는 요소의 높이 값을 초기화한다.
methods: {
  setHeight(el) {
    el.style.height = 'auto';
  },
  resetHeight(el) {
    el.style.height = null;
  }
}
  • setHeight 메서드에서 el 인자는 트랜지션으로 들어오는 요소를 나타내고 resetHeight 메서드도 동일한 방식으로 작동한다.
    이렇게 구현하면 요소가 애니메이션을 마친 후 최종 높이 값을 자동으로 설정한다.
profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글