워캣의 공간 리스트 뷰를 개발하는 과정에서 Safari, Naver 웹뷰, 데스크톱 등 다양한 브라우저 환경에 대응해야 했습니다.

크롬이나 네이버, 카카오 웹뷰 등의 브라우저는 멀쩡한데 사파리는 항상 예외적입니다.
위 사진처럼 Safari 브라우저는 하단에 탭바가 있기 때문에 해당 영역 높이만큼 컨텐츠가 줄어들게 됩니다.

따라서 리스트 컨텐츠를 감싸고 있는 Wrapper의 height를 조절해주어야 합니다.
현재 유저가 보고 있는 브라우저가 어떤 브라우저인지를 구별해서 사파리일 때와 아닐 때 각기 다른 height를 적용해 보겠습니다.
<div className="mt-4 h-[calc(100vh-150px)] overflow-y-scroll scrollbar-hide">
{placeList.map((data) => (
<PlaceItem data={data} />
))}
</div>
공간 리스트 뷰의 코드 일부입니다.
업무 공간 데이터 배열인 placeList를 div 태그가 감싸고 있어요.
여기서 h-[calc(100vh-150px)] overflow-y-scroll에 의해서
공간 리스트의 전체 높이가 Wrapper의 height보다 길다면 스크롤할 수 있습니다.
이 때 150px만큼 빼서 마지막 PlaceItem 컴포넌트가 짤리지 않게 해줬어요.
그런데 앞서 언급했듯이 사파리는 하단에 탭바가 존재하기 때문에
150px만으로는 부족하고 230px 정도를 짤라주어야 했습니다.
// global.css
@supports (font: -apple-system-body) and (-webkit-appearance: none) {
#place-list {
height: calc(100vh - 230px);
}
}
전역 CSS에 @-규칙을 이용한 코드를 추가합니다.
iOS이면서 Safari 버전 15 이상인 경우에만 적용되는 CSS에요.
워캣은 스타일링 라이브러리로 tailwind를 사용 중이라서 class를 만들어 적용하게 되면 className이 겹치기 때문에, 사파리 브라우저일 때만 id를 새롭게 추가해주는 방식으로 구현했습니다.
<div
ref={placeListWrapperRef}
className="mt-4 h-[calc(100vh-150px)] overflow-y-scroll scrollbar-hide">
{placeList.map((data) => (
<PlaceItem data={data} />
))}
</div>
먼저 Wrapper 태그에 Ref를 추가합니다.
렌더링이 되었을 때 div 태그에 접근하기 위해서 ref를 사용했어요.
DOM에 접근하는 또 다른 방법인 document.querySelector()를 써도 상관없지만
ref를 직접 Wrapper에 붙여 참조하기 더 편해서 React Hook인 useRef를 이용했습니다.
useEffect(() => {
if (placeListWrapperRef.current) {
const userAgent = navigator.userAgent.toLowerCase();
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isNaver = /naver/i.test(userAgent);
if (isSafari && !isNaver) {
// set id only safari
placeListWrapperRef.current.id = 'place-list';
}
}
}, []);
ref.current가 존재하면 navigator.userAgent에 접근해서 사용자 에이전트 문자열을 가져옵니다.
👉 navigator.userAgent 란?
사용자 에이전트(user agent)란 사용자를 대표하는 컴퓨터 프로그램을 의미하는데, 웹 맥락에선 '브라우저'를 의미합니다. userAgent 문자열은 브라우저 종류를 포함합니다.
참고 MDN
정규식을 이용해서 userAgent 문자열이 특정 브라우저를 포함하고 있는지 알아냅니다.
// 동일한 동작
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isSafari = /safari/i.test(userAgent);
정규표현식의 앞부분은 chrome, android를 제외한 문자열에서 safari라는 문자열을 찾겠다는 의미입니다.
코드의 일관성과 가독성을 위해서 isNaver와 같이 간략하게 선언하는게 좋아보이네요.
조건식에 isSafari만 넣지 않고 !isNaver도 추가해주었습니다.
네이버 웹뷰에서도 isSafari가 true로 출력되기 때문입니다.
mozilla/5.0 (linux; android 11; sm-a908n build/rp1a.200720.012; wv) applewebkit/537.36 (khtml, like gecko) version/4.0 chrome/80.0.3987.163 whale/1.0.0.0 crosswalk/25.80.14.29 mobile safari/537.36 naver(inapp; search; 1000; 11.6.7)
🔼 네이버 웹뷰에서 userAgent를 콘솔 찍어보면 위와 같이 출력됩니다.
각 브라우저마다 userAgent에 여러가지 브라우저 종류를 포함하고 있기 때문에
조건을 구체적으로 부여해야 더 정확한 브라우저 구별이 가능할 것 같습니다.
브라우저 별 userAgent 문자열을 알고 싶다면 이 블로그를 참고해보세요.
사파리에만 잘 적용되는지 확인하기 위해 #place-list에 css를 추가해봅니다.
#place-list {
height: calc(100vh - 230px);
border: 1px solid red;
}

사파리 브라우저에서만 border가 추가된 걸 확인할 수 있습니다.
지금까지 userAgent를 사용하여 브라우저나 웹뷰를 판별하는 크로스 브라우징에 대해 알아보았습니다. 사용자 에이전트만으로는 완벽한 판별이 가능하지는 않기 때문에 주의해야 합니다.
웹 애플리케이션이 다양한 브라우저 및 환경에서 일관되게 동작하도록 보장하는 것이 중요할 것 같습니다.