Post

IOS에서 페이지 뒤로가기

들어가며

오늘은 26일이라.. 연차를 쓰신 분들이 많기도 하고.. 연말이니 정리겸 오랜만에 개발 블로그를 적어보려 한다 ㅎ

커뮤니티를 개발하면서 AI의 도움을 많이 받았으나, AI가 아직 웹뷰 환경에서의 디버깅은 잘 못 도와주는 것 같다는 생각을 많이 받았었다. 여러 일들(바탐시트와 헤더의 z-index 문제라든가, AOS에서 최근 검색어 버튼이 눌리지 않는 현상, 앱브릿지 연동 …etc)이 있었지만 오늘은 그 중 IOS 환경에서 커스텀 모달 창의 확인 버튼을 누를 시 페이징 뒤로가기 동작이 안 됐던 사건에 대해 풀어보려 한다.

문제 상황

커스텀 모달 창에 있는 확인 버튼을 누르면 이전 페이지로 이동이 되어야 하는데 이게 Chrome이나 Android 환경에서는 잘 동작했는데, Safari(iOS)에서만 뒤로가기가 동작하지 않는 문제가 발생했다.

문제의 원인은 WebKit 엔진(Safari)Blink 엔진(Chrome)popstate 이벤트 처리 순서가 다르기 때문이었다.

이벤트BlinkWebKit
hashchange → history 스택 적용거의 즉시렌더 안정되기 전엔 이벤트/스택 업데이트를 멈춤
popstate 처리 우선순위렌더링과 별도 스케줄렌더 완료 이후로 밀릴 수 있음

무슨 일이 일어났나?

  1. Confirm modal이 오픈되면 window.location.hash#confirm-open을 붙인다
  2. hashchange 이벤트가 발생하면서 history 스택에 새 항목이 쌓인다
  3. iOS 환경에서는 hashchange 이벤트가 popstate 이벤트로 즉시 연결되지 않는다
  4. UI 업데이트와 history.back 명령이 한 이벤트 안에서 동시에 수행되면 history.back 블록이 무시된다

왜 그런가?

  • Safari는 WebKit 엔진을 사용하고 있기에 hashchange 이벤트는 즉시 처리되지만, popstate는 히스토리가 안정됐을 때만 발생한다
  • UI 변경과 히스토리 변경이 같은 이벤트 루프에서 일어나면 popstate 동작이 스킵되면서 뒤로가기가 안 일어나는 것이다

해결 방법 1: setTimeout

첫 번째 해결 방법은 setTimeout을 사용하는 것이다.

1
2
3
4
window.close();
setTimeout(() => {
  window.history.back();
}, 100);

setTimeout을 사용하면 history.back을 이벤트 큐에 넣었다가 콜스택이 비었을 때 실행시킨다. 고로 이렇게 하면 UI 렌더링과 history 조작 동작이 겹치지 않아 정상적으로 동작한다.

근데… 다들 알다시피 setTimeout은 남용하면 위험하다. 그 이유는 다음과 같다.

  1. setTimeout(fn, 100)은 0.1s 뒤에 해당 함수가 무조건 실행되게 하는게 아니라 최소 0.1s 뒤에 실행 되는걸 보장하는 함수다.
  2. 왜냐면 자바스크립트는 싱글 스레드 언어이므로 하나의 콜스택 안에서 이벤트들이 순차적으로 실행된다.
  3. 근데 내가 이 컨펌창에서 setTimeout을 쓰고, 다른 컴포넌트에서도 setTimeout을 쓰면 여러 타이밍들이 겹칠 수도 있고,
  4. 만약 콜스택에서 0.1s 이상이 걸리는 무거운 연산을 (cpu 사용량이 많은 작업) 진행하고 있다면
  5. setTimeout의 콜백 함수는 타이머 만료 후 태스크 큐에 들어가고, 무거운 연산이 끝나 콜스택이 비었을 때 이벤트 루프가 큐에서 콜스택으로 옮기므로 0.1s 이상의 시간이 걸린 뒤에 페이지가 이동될 수 있다.

해결 방법 2: requestAnimationFrame

고로 더 좋은? 안전한? 방법은 requestAnimationFrame을 활용하는 것이다!

1
2
3
4
5
6
7
8
9
onConfirm: () => {
  // 모달 닫기
  closeModal();
  requestAnimationFrame(() => {
    overlay.unmountAll();
    // 페이지 뒤로 가기
    window.history.back();
  });
}

requestAnimationFrame이란?

브라우저는 초당 60번 정도(화면 주사율에 따라 다르지만) 빠르게 그림을 계속 바꾸며 화면을 보여준다. 이때 화면이 바뀌기 직전에 실행될 함수를 콜백에 넘겨줄 수 있는데 이것이 바로 requestAnimationFrame이다.

브라우저의 렌더링 과정

브라우저에서 UI 변경은 크게 아래의 두 단계로 나뉜다

  1. Layout 또는 Style 업데이트를 준비하는 단계
  2. 실제로 화면에 그리는 Paint 단계

따라서 requestAnimationFrame을 사용하면

  1. overlay.close() - DOM 변경
  2. requestAnimationFrame(callback) 등록
  3. overlay.close() - 리플로우
  4. requestAnimationFrame(history.back) 실행
  5. overlay.close() - 페인팅

이렇게 렌더링 사이클을 명확히 분리할 수 있어 setTimeout보다 더 예측 가능하고 정확한 타이밍 제어가 가능하다.

번외: Safari에서의 _blank

Safari에서는 _blank로 연 새 창의 히스토리 스택이 1이 아닌 2부터 시작한다

참고: https://github.com/whatwg/html/issues/6491

번외 2: ai를 잘 쓴다는건 뭘까..

요근래 ai를 잘 쓴다는게 대체 뭘 의미하는걸까 곰곰히 생각하게 되는데.. 내가 모르는 도메인의 영역까지 빠르게 이해하고 구현해서 업무 효율을 높이는게 지금 세대에서 ai를 잘 쓴다는 의미인 것 같다 흠..

This post is licensed under CC BY 4.0 by the author.