본문 바로가기
Vue/Vuetify

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

by kdg99 2023. 6. 9.

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

 

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

댓글