V 컬러링은 가로모드 서비스를 지원하지 않고 있습니다. 그래서 기존의 window.orientation
값으로 가로 세로 여부를 판단하여 가로 모드로 전환했을 경우 css를 이용하여 가로 모드는 지원하지 않는다는 문구로 화면을 덮고 있는데요, 코드 품질 개선을 위해 사용 중인 sonarqube에서 orientation is deprecated (더 이상 지원하지 않는다는) 오류가 나와 해결책을 찾아보았습니다.
검색해 보니 가장 먼저 screen.orientation
이 해결책으로 나왔습니다. V 컬러링은 타입스크립트를 사용 중이기 때문에 screen을 바로 인식하지는 못해 window.screen
으로 한 번 선언해 주었습니다.
const { screen } = window;
screen.addEventListener('change', setMode);
setMode()
const setMode = () => {
if (screen.orientation.type === 'landscape-primary') {
document.body.classList.add('interrupt'); // 가로모드
} else {
document.body.classList.remove('interrupt');
}
};
이렇게 하고 안드로이드 웹에서 테스트를 해 보니 아주 잘 작동하여 흐뭇한 마음으로 커밋을 하려다 혹시 몰라 아이폰에서도 한 번 더 테스트해 보았습니다. 그랬더니 공포의 하얀 화면이 나오더군요. ㄷㄷ 원인을 알아보니 iOS에서는 screen.orientation
을 빈 값({})으로 인식하고 있어서, addEventListener(’change’)
를 잘못된 함수로 인지하여 에러가 발생하는 것이었습니다.
(screen.orientation
은 safari를 지원하지 않습니다. TT)
보편적인 방법으로 등장하는 window.innerWidth > window.innerHeight
로 체크하는 것도 고려해 보았습니다만 V 컬러링의 정책은 단순히 가로가 길 경우가 아니라 기기를 수직으로 세웠을 경우 지원을 하지 않는 부분이기 때문에, 간혹 가로가 더 긴 아이패드(?) (현재는 존재하지 않습니다만...) 등이 출시될 경우를 대비하여 위 방법은 사용하지 않기로 하였습니다. 무엇보다도 저 코드에 굳이 OS 분기 처리를 하고 싶지 않았습니다... 😢
열심히 삽질 끝에 window.matchMedia('(orientation:landscape)')
라는 아주 훌륭한 기능이 있다는 사실을 발견하였습니다. 내부 속성에 .matches라는 값이 boolean 형태로 가로 세로 여부를 리턴해 줍니다. 바로 적용해 보았습니다.
const m = window.matchMedia('(orientation:landscape)');
if (m.matches) {
document.body.classList.add('interrupt');
} else {
document.body.classList.remove('interrupt');
}
초기 진입 시 가로모드일 경우를 위해 위 코드를 넣어 주었으나, change event를 받았을 때도 처리를 해야 하기 때문에 setMode와 같이 따로 함수를 분리하여 구현해 보았습니다.
const setMode = (isLandscape: boolean) => {
if (isLandscape) {
document.body.classList.add('interrupt'); // 가로모드
} else {
document.body.classList.remove('interrupt');
}
};
const m = window.matchMedia('(orientation:landscape)');
setMode(m.matches);
const setEventForChangeMode = e => setMode(e.matches);
m.addEventListener('change', setEventForChangeMode);
최초 진입과 사용자의 변경을 구분하기 위해 위와 같이 구현하였습니다. 테스트를 해 보니 아주 잘 작동하긴 하는데 이상한 문제가 발생했습니다. swiper에서 배너 정렬이 이상하게 되는 이슈였습니다.
원인을 정확히 알아내지는 못했으나, 다른 화면의 swiper에서도 비슷한 현상이 나타나는 것으로 보아 위의 change event와 무언가 충돌이 나는 것 같았습니다. 마침 홈에서 resize 시 호출되는 이벤트가 있었고, 가로 세로 모드 전환은 resize event를 무조건 탈 것이라 생각하여 (이게 어떻게 보면 더 맞는 것 같기도?) resize 함수에 로직을 다시 넣어 보았습니다.
(추가 : 콘솔에서 확인해 보니 change event가 발생할 경우 body의 height 값이 임의로 조정됩니다. 이 때문에 플레이어에서도 영상이 깨지는 문제가 발생합니다.)
const resizeHeight = () => {
const m = window.matchMedia('(orientation:landscape)');
if (m.matches) {
document.body.classList.add('interrupt');
} else {
document.body.classList.remove('interrupt');
}
};
window.addEventListener('resize', resetHeight);
resetHeight();
이렇게 했더니 두둥! 아무 이상 없이 모든 화면에서 아주 깔끔하게 잘 동작하는 것을 확인할 수 있었습니다.
2022년 5월 10일에 작성함.
(+) trouble shooting
resize event에 위 로직을 적용했더니 치명적인 문제가 발생했습니다. 바로 키패드가 올라오는 것과 같이 resize event가 발생할 경우에도 가로가 세로보다 길어져서 무조건 가로모드로 인식하는 문제였습니다. 현재는 기존 코드로 롤백해 둔 상태입니다. orientationchange의 완벽한 대체 방법은 과연 없는 걸까요?
우선 change
event를 걸었을 때 위와 같은 오류가 발생하는 원인은, 기존의 orientationchange
event는 resize
보다 먼저 실행이 되어 높이값에 영향을 주지 않았던 반면에 window.matchMedia
의 change
event는 resize
이후에 실행이 되어 버려 body의 높이에 영향을 주기 때문이었습니다. 이 문제를 해결하기 위하여 window.dispatchEvent()
를 이용하여 이벤트 순서를 동기적으로 조절하고자 시도하였으나, 이벤트의 주체가 window가 아니라서 그런지 여전히 resize
event가 먼저 발생하였습니다.
현재로서는 orientationchange
event를 대체할 만한 방법이 없는 것으로 파악되어, 우선 sonarqube의 window.orientation is deprecated 오류를 피하고자 다음과 같이 코드를 수정하였습니다.
const invokeInitEvent = () => {
const changeEvent = new CustomEvent(CHANGE_EVENT);
const resizeEvent = new CustomEvent(RESIZE_EVENT);
window.dispatchEvent(changeEvent);
window.dispatchEvent(resizeEvent);
};
const setInterrupt = e => {
const { orientation } = e.currentTarget;
if (orientation === 0 || orientation === 180) {
document.body.classList.remove('interrupt');
} else {
document.body.classList.add('interrupt');
}
};
const resetHeight = () => {
...
};
window.addEventListener(CHANGE_EVENT, setInterrupt);
window.addEventListener(RESIZE_EVENT, resetHeight);
invokeInitEvent();
꼼수긴 하지만 이렇게 하니 코드스멜에서는 걸러지네요. window.dispatchEvent()
로 관리하는 것이 가독성 측면에서도 더 좋은 것 같습니다.
2022년 5월 12일에 수정함.