[Navigation Drawer] 1264px에서 자동으로 접히는 문제 Mobile-Breakpoint
위는 Vue랑 포트폴리오 연습용으로 만든 웹서버인데 메뉴가 적당한 크기에서 알아서 접히는 구조다.
지인에게 보여주려고 모바일로 접속하고 보니 사이드메뉴가 접혀서 보이지 않았다.
그래서 접힌 메뉴를 열 수 있는 버튼을 만들어주고, 원하는 크기에서 메뉴가 자동으로 접힐 수 있도록 했다.
MainPageView에 표시될 MainPageComponent의 템플릿 구조로 헤더(app bar) + 메뉴(nav drawer) + 메인(v-card)로 이뤄져 있다.
일단 헤더에 버튼을 추가해주기 위해 Navigation drawer 항목에서
<v-app-bar-nav-icon variant="text" @click="$emit('stop')"></v-app-bar-nav-icon>
메뉴 버튼을 가져와서 이벤트를 붙여 줬다.
drawer 변수를 MainPage에 둘 것이기 때문에 $emit을 이용해 부모 컴포넌트에 이벤트를 발생시키고
MainPage에서 이벤트가 발생하면 stopHandler를 실행시키도록 했다.
실질적으로 drawer 변수를 사용하는 nav drawer는 Menu 컴포넌트에 있기 때문에
Menu 컴포넌트에 바인딩해서 Computed를 이용해 사이드 메뉴를 키고 끌 수 있게 해줬다.
//Menu 컴포넌트의 script
<script setup>
import { computed } from 'vue';
const props = defineProps({
drawer: Boolean,
});
const drawer = computed(() => {
return props.drawer;
});
</script>
setup이 편해서 setup을 이용한 props와 emit을 정리해뒀던 걸 요긴하게 써먹었다.
(공식 문서나 다른 사람의 코드를 찾아봐도 아직 script setup으로는 예제가 별로 없다...)
이렇게 메뉴를 여닫는 건 성공했고 원하는 크기에서 메뉴가 여닫힐 수 있도록 옵션을 찾아봤다.
공식 문서에서 찾을 수 없어 window의 width height값을 감지해 원할 때 drawer값을 바꿔주기로 했다.
<template>
<v-card ref="el">
...
</v-card>
</template>
<script setup>
import { useResizeObserver } from '@vueuse/core'
...
const el = ref(null)
useResizeObserver(el, (entries) => {
const [entry] = entries
const { width, height } = entry.contentRect
if(width <= 900){
drawer.value = false;
}else if (width > 900){
drawer.value = true;
}
})
</script>
if문 작동은 잘 되는데 메뉴가 1260px 근처에서 자꾸 접혀서 공식문서를 뒤져본 결과
메뉴컴포넌트의 nav drawer에 disable-resize-watcher 옵션을 추가하니 메뉴를 원하는 크기에서 접을 수 있었다.
그런데 메뉴는 안 접히지만
여전히 1264px에서 scrim창이 떠서 본문을 선택하지 못 하도록 가리고
메뉴창의 width만큼 있던 메인부분의 margin이 사라져서 메뉴창과 메인창이 겹치게 되었다.
scrim 관련 옵션들을 찾아보고 적용해봐도 작동하지 않아서 drawer, scrim, overlap,1260px, 1264px등을 키워드로 넣고 찾아보니 Mobile-Breakpoint라는 키워드를 찾을 수 있었다.
관련글들을 보면 꽤 오래전부터 있던 악성 기능이었던 것 같은데 옛날 글이니만큼 재현코드도 전부 닫혀있고 옛날 버전을 이용한 코드라 적용할 수 없는 것들이 대부분이었다.
//메뉴의 scoped style
div.v-navigation-drawer__scrim{
display: none;
}
그래서 그냥 개발자도구를 이용해 scrim창을 없애주고
본문이 접히는 건 본문에 적용되는 margin을 임의로 적용해주기로 했다.
//템플릿
<v-main v-bind:style="styleObject" image="@/assets/background-hanji3.jpg">
<div class="main-wrap">
<router-view />
</div>
</v-main>
//스크립트
<script setup>
...
let styleObject = {
"min-height": "300px",
"--v-layout-left": "250px",
};
...
const stopHandler = ()=>{
drawer.value = !drawer.value;
if(drawer.value){
styleObject["--v-layout-left"] = "250px";
}else{
styleObject["--v-layout-left"] = "0px";
}
};
const el = ref(null)
useResizeObserver(el, (entries) => {
const [entry] = entries
const { width, height } = entry.contentRect
if(width <= 900){
drawer.value = false;
styleObject["--v-layout-left"] = "0px";
}else if (width > 900){
drawer.value = true;
styleObject["--v-layout-left"] = "250px";
}
})
</script>
저런 옵션도 쌍따옴표로 감싸주니 적용이 되더라
옵션을 임의로 고정해주니 더 이상 1264px에서 움직이지 않았다.
그런데 이제는 if문에 넣어둔 width의 근처와 그 밑에서 메뉴가 열리지 않았다.
아마 메뉴가 열리는 애니메이션동안 --v-layout-left값이 바뀌면서 if문이 실행되는 것 같은데
로그를 찍어봐도 왜 그러는지 알 수가 없었다.
그래서 그냥 메뉴가 여닫히는 동안에는 Observer가 작동하지 않도록 변수를 추가해주기로 했다.
//MainPage의 script 전문
<script setup>
import Header from "@/components/_Header.vue";
import Menu from "@/components/_Menu.vue";
import { ref } from "vue";
import { useResizeObserver } from '@vueuse/core'
let check = true;
let drawer = ref(true);
let styleObject = {
"min-height": "300px",
"--v-layout-left": "250px",
};
const stopHandler = ()=>{
drawer.value = !drawer.value;
if(drawer.value){
check = false;
styleObject["--v-layout-left"] = "250px";
setTimeout(()=>{
check=true;
}, 1000);
}else{
check = false;
styleObject["--v-layout-left"] = "0px";
setTimeout(()=>{
check=true;
}, 1000);
}
};
const el = ref(null)
useResizeObserver(el, (entries) => {
const [entry] = entries
const { width, height } = entry.contentRect
if(width <= 900 && check){
drawer.value = false;
styleObject["--v-layout-left"] = "0px";
}else if (width > 900 && check){
drawer.value = true;
styleObject["--v-layout-left"] = "250px";
}
})
</script>
원래는 스타일 변경문 전 후로 check = false, true만 해뒀었는데
애니메이션이 재생되는 동안 자꾸 Observer가 작동하길래 setTimeout처리까지 해줬다.
깔끔하고 명쾌한 답안은 아니지만 작업하면서 알게 된 점이 있고 같은 문제를 겪는 분들이 있을 것 같아 글로 남긴다.