본문 바로가기

Dev/[Vue.js]

[Javascript+Vue.js] Scrollbar 여부에 반응하는 엘리먼트 만들기(with Class and Style Binding)

반응형

 

INTRO


 

웹 페이지를 개발하다보면, 유효성 체크를 위해 기존 엘리먼트에 별도의 아이콘을 배치하는 경우가 있다.

네이버 회원가입 페이지에서 input 태그 내 자물쇠 아이콘이 배치되어 있다.

본 포스팅에서는 아이콘과 스크롤바가 겹치는 현상을 해결한 경험을 공유한다.

 

 


 

1. 하고자 했던 것

- 위의 네이버 예시를 보면, 자물쇠 모양은 해당 필드의 유효성 통과 유무에 따라 달라진다.

- 프로젝트 진행 도중, 

- input 이 아닌 text area에 해당 기능을 똑같이 구현해야 할 일이 생겼다.

- 그래서 v-model로 바인딩 된 변수를 감지하며 유효성 체크 로직을 적용하였고, 

- 유효성 검증 통과 여부에 따라 동적으로 style을 다르게 적용하여 해결.

 

:class="{
  'check-ok': isValid === true,
  'check-false': isValid === false,
}"

 

 

2. 문제의 시작

- 아이콘 동적 삽입까진 OK.

- 그러나 해당 필드에 스크롤 바가 생길 경우, 이 아이콘과 겹쳐 가려지는 현상이 발생

- 그래서 스크롤바 유무에 따라 아이콘의 위치가 변경되도록 하고싶었다.

 

3. 문제의 해결

- 아래와 같은 절차로 해결

1. ref 로 해당 엘리먼트를 가져온다.

2. 엘리먼트를 감시하면서, 스크롤바 유무를 파악(scrollHeight, clientHeight 활용)

3. 스크롤 바 유무에 따라 다른 style을 매핑

 

가) ref로 해당 엘리먼트 가져오기

- 아래와 같이 태그의 엘리먼트를 변수로 가져올 수 있다.

<template>
  <main>
    <textarea ref="textArea"></textarea>
  </main>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const textArea = ref();
</script>

 

 

나) 엘리먼트를 감시하면서, 스크롤 바 유무를 파악한다.

- @input 을 통해 change 이벤트를 등록하거나, v-model로 변수를 바인딩하고 이 변수의 변경을 감지하는 로직을 추가한다.

- 필자의 경우 v-model을 사용하고, watch로 감지.(이 방법은 주로 사용하는 패턴대로..)

<template>
  <main>
    <textarea ref="textArea"  v-model="textAreaValue"></textarea>
  </main>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

const textArea = ref();
const textAreaValue = ref('');

watch(textAreaValue, () => {
  console.log('여기서 감지한다.');
});

</script>

- ref를 통해 가져온 엘리먼트의 scrollHeight, clientHeight를 watch 안에서 참조하면서, 스크롤 생성 유무를 파악한다.

watch(textAreaValue, () => {
  if (textArea.value.scrollHeight > textArea.value.clientHeight) {
    // 스크롤바가 생성된 상태
  } else {
    // 스크롤바가 없는 상태
  }
});

 

다) 스크롤 바 유무에 따라 다른 style을 매핑

-vue.js에서는 class와 style을 동적으로 매핑하기 위한 방법이 존재한다.

https://vuejs.org/guide/essentials/class-and-style.html

 

Class and Style Bindings | Vue.js

 

vuejs.org

-이 방법을 토대로 style을 변경해준다.(별도의 css파일로 class에 css를 매핑시켜놓은 경우, class 를 변경시키면 된다)

<template>
  <main>
    <textarea ref="textArea" :style="styleObject" v-model="textAreaValue"></textarea>
  </main>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

const textArea = ref();
const textAreaValue = ref('');
const styleObject = ref({
  width: '600px',
  height: '100px',
  backgroundImage: "url('/src/assets/ok.svg')",
  backgroundPosition: 'right 3px center',
  backgroundRepeat: 'no-repeat',
});
watch(textAreaValue, () => {
  let position = '';

  if (textArea.value.scrollHeight > textArea.value.clientHeight) {
    // 스크롤바가 생성된 상태
    position = '25';
  } else {
    // 스크롤바가 없는 상태
    position = '3';
  }

  styleObject.value = {
    width: '600px',
    height: '100px',
    backgroundImage: "url('/src/assets/ok.svg')",
    backgroundPosition: `right ${position}px center`,
    backgroundRepeat: 'no-repeat',
  };
});
</script>

 

 

4. 결과

 

 

마무리

ref를 통해 엘리먼트에 접근할 경우, 매우 다양한 일을 할 수 있다.

다만 vue.js에서는 ref를 무분별하게 사용할 경우 state 추적이 번거로워지며, lifecycle의 이해도가 부족하면 오류 발생 확률이 높아짐.

이러한 경우 처럼 엘리먼트에 접근하여 속성을 get 할 경우에는 다른 방법이 없으므로 사용하지만, 가급적 vue의 기능들 활용하는것이 좋겠다.

 

 

-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!

 

반응형