Vue/Vuetify

[Navigation Drawer] 1264px에서 자동으로 접히는 문제 Mobile-Breakpoint

kdg99 2023. 6. 9. 09:21

kdg99.link

 

위는 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처리까지 해줬다.

 

깔끔하고 명쾌한 답안은 아니지만 작업하면서 알게 된 점이 있고 같은 문제를 겪는 분들이 있을 것 같아 글로 남긴다.