[Vue.js] 웹브라우저 뒤로가기시 라우팅 변경 없이 모달만 닫히게 하기

kim unknown·2024년 5월 12일
0

Vue.js

목록 보기
2/2
post-thumbnail

웹개발을 하다보면 자주 접하게 되는 UI 중 하나가 모달 혹은 팝업이다. 그런데 이런 모달이나 팝업을 띄웠을 때 고려해야 하는 것 중 하나가 바로 뒤로가기 버튼이다.

UX적으로 생각해보았을 때 모달은 페이지 변경이 아닌 레이어 위에 쌓이는 형태이기 때문에 라우팅 변경없이 띄워지고 사라질 때로 라우팅 변경없이 모달 창만 사라지는게 가장 자연스럽다고 생각했다.

또한, 만약 이 모달이 띄워진 상태에서 유저가 브라우저 창의 뒤로가기 버튼을 눌렀을 시에도 라우터가 뒤로가는 게 아닌 모달창이 닫히는 게 자연스러운 사용자 경험을 제공할 것이라 생각했다. 이러한 고민을 바탕으로 내가 어떤 식으로 모달을 관리했는지 공유해보려고 한다.

내가 생각한 로직은 다음과 같다.

  • 모달을 열 때는 store에 현재 여는 모달을 기록하고 history를 한 번 쌓아준다.
  • 모달을 store에 기록하는 이유는 브라우저 상에서 모달이나 팝업 등이 중첩으로 열리는 경우도 있기 때문에 이를 전역적으로 상태관리를 하기 위함이다.
  • 브라우저 상에서 뒤로가기/닫기 액션이 이루어졌을 때는 store에 담아둔 모달 리스트를 체크하고 띄워져 있는 모달이 있다면 모달만 닫는다. 이 때 쌓아둔 히스토리를 제거한다.

1. 모달 관리 스토어 생성하기

// modal 관리를 위한 스토어 생성
export default {
  name: 'modal',
  state() {
    return {
      modalList: [] // 쌓인 모달을 담을 배열
    };
  },
  getters: {
    getModalList(state) {
      return state.modalList;
    }
  },
  mutations: {
    // 모달이 띄워질 때마다 modalList에 추가
    ADD_MODAL_LIST(state, value) {
      state.modalList.push(value);
    },
    // 모달이 닫힐시 modalList에서 제거
    REMOVE_MODAL_LIST(state, value) {
      state.modalList.splice(value, 1);
    },
    // modalList 제일 뒤에 있는 요소 제거
    POP_MODAL_LIST(state, value) {
      state.modalList.pop();
    }
  },
  actions: {
    // 현재 닫으려고 하는 모달을 찾아 modalList에서 제거
    // 다만 동일한 모달이 여러 개가 띄워졌을 경우 가장 위에 쌓인 하나만 제거 되도록 함
    removeLastModal({ getters, commit }, modalElement) {
      const modalList = window.$nuxt.$store.getters['router/getModalList'];
      const findIndex = modalList.indexOf(modalElement);
      if (findIndex > -1) {
        window.$nuxt.$store.commit('router/REMOVE_MODAL_LIST', findIndex);
      }
    }
  }
};

2. 모달 open, close 공용함수 만들기

computed: {
  ...mapGetters({
    modalList: 'modal/getModalList'
  })
},
methods: {
  ...mapMutations({
    ADD_MODAL_LIST: 'modal/ADD_MODAL_LIST',
    POP_MODAL_LIST: 'modal/POP_MODAL_LIST'
  }),
  ...mapActions({
    removeLastModal: 'modal/removeLastModal'
  })
  ...
}
// 모달, 팝업 띄우기
openModalHandler(component, props = {}, actions = {}) {
  /* 컴포넌트 띄우는 함수(openHandler) 실행 시 mount와 close 이벤트 액션을 넘김 */
  return this.openHandler(component, props, { ...actions,
    // mount event action
    mount: (componentEl) => {
      // 컴포넌트가 마운트 되면 mount 이벤트를 emit
      // history 쌓기
      window.history.pushState(null, '', window.location.href);
      this.ADD_MODAL_LIST(componentEl); // store에 마운트된 컴포넌트 담기
    },
    // close event action            
    close: (componentEl) => {
      // 모달 내부적으로 존재하는 닫기 버튼을 누르면 close 이벤트 emit
      // 컴포넌트 제거 함수 (closeHandler) 실행하여 띄워진 요소 닫기
      this.closeHandler(componentEl);
      this.removeLastModal(componentEl); // store removeLastModal 함수 실행
    }
  });
}


// 브라우저 뒤로가기 시 모달, 팝업만 닫기
closeModalHandler() {
  // 가장 위에 쌓인 모달 요소 찾기
  const modalEl = this.modalList[this.modalList.length - 1];
  // 찾은 모달 닫기 액션
  this.closeHandler(modalEl);
  this.POP_MODAL_LIST(); // store에서 modal 제거
}

3. 브라우저 뒤로가기 이벤트 감지

브라우저 페이지 이동 시에 발생하는 onpopstate 이벤트를 활용하여 브라우저 뒤로가기 이벤트를 감지시 예외처리를 해주었다.

// 브라우저 레이어가 mount 됐을 때 popstate 이벤트 핸들러 등록
mounted() {
  window.addEventListener('popstate', this.historyHandler);
},
beforeDestroy() {
  window.removeEventListener('popstate', this.historyHandler);
},
  
...

// 브라우저 뒤로가기 클릭시 모달만 닫히도록 예외처리
historyHandler() {
  // 스토어 modalList length를 체크하여
  // 떠 있는 팝업이 존재하면 팝업만 닫히게 closeModalHandler 실행
  if (this.modalList.length > 0) {
    this.closeModalHandler();
  }
}
    
profile
과거의 나에게 묻기 위한 기록

0개의 댓글