INTRO
웹 페이지를 개발하다보면, 유효성 체크를 위해 기존 엘리먼트에 별도의 아이콘을 배치하는 경우가 있다.
본 포스팅에서는 아이콘과 스크롤바가 겹치는 현상을 해결한 경험을 공유한다.
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의 기능들 활용하는것이 좋겠다.
-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!
'Dev > [Vue.js]' 카테고리의 다른 글
[Vue.js] v-model로 한글을 처리하는 방법에 대하여 (0) | 2023.03.03 |
---|---|
[Vue.js] vue 3.0 + Typescript 에서 vue-router적용기 (0) | 2022.06.13 |
[Vue.js] vue 2.0 에서 LifeCycle에 대한 삽질 후기 (Error in mounted hook) (0) | 2022.05.15 |
[vue.js] view에 바인딩 된 '배열' 변수를 업데이트 할 때, 감지 안되는 문제 (0) | 2022.05.12 |
[Vue.js] parent <-> child간 양방향 바인딩(with custom Tag) (0) | 2022.04.26 |