OS와 브라우저 별 Scrollbar 대응

안녕하세요, LINER에서 Web Frontend를 담당하고 있는 제니🍑입니다. 입사한 지 두 달이 채 되지 않았지만 LINER Web Home 의 눈코 뜰 새 없었던 출시 준비부터 실제 출시 과정, 그리고 안정화까지, 짧은 시간 동안 정말 많은 경험을 했습니다.

요즘은 출시 이후로 미뤄졌던 기능들과 UX적인 개선 사항들에 대응을 하고 있는데요, 오늘은 그 중 LINER Web Home의 Scrollbar Styling 에 관한 저의 고민, 그리고 경험을 공유 드려볼까 합니다.

What? (문제 상황)


Web home에서 Scrollbar에 관련된 문제 상황이 크게 두 가지가 있었습니다.

1. Windows 와 Mac OS 의 Scrollbar Style이 통일 되지 않았던 UI적인 문제

2.’스크롤 자동 숨기기’ 설정을 해놓은 User와 그렇지 않은 User들의 UX 차이

조금 덧붙이자면,

1번. Windows와 Mac OS는 Default 제공되는 Scrollbar는 색, 굵기 등이 다릅니다.

Windows 같은 경우에는 다음과 같이 회색 배경 Scroll track, 그리고 조금 더 진한 회색의 Scroll thumb (실제 스크롤을 하는 막대를 thumb이라고 부릅니다) 그리고 꺾새로 표시된 Scrollbar button 이 있는 것을 확인할 수 있습니다.

[참고 1] Windows 의 기본 Scrollbar Style
[출처] https://troubleshooter.xyz/wiki/always-show-scrollbars-in-windows-store-apps/

Mac OS 는 Scroll track 이 투명하게 보이지 않으며, 진회색의 얇고 둥근 Scroll thumb, 그리고 Scroll button이 부재합니다.

[참고 2] MacOS의 기본 Scrollbar Style

개발과 테스팅을 맥북으로만 하다보면 이런 차이를 간과하게 되는데, 제가 해야 했던 일은 Windows에서도 Mac OS 스타일의 Scrollbar를 구현 하는 일이었습니다.

(p.s 특이하게도 Naver Whale 브라우저는 이미 윈도우에서도 브라우저 단에서 스크롤 형태를 Mac OS 스타일로 보여주고 있었습니다)

2번. Scrollbar로 인해서 화면이 ‘덜컹’ 거리는 문제

말로 설명하면 크게 와 닿지 않을 것 같아서 동영상을 첨부했습니다.

위와 같은 문제는 흔히 발생할 수 있는 상황인데요, 많은 웹사이트에서 modal이 띄워 졌거나, sidebar가 열려 있거나, search bar 같은 것을 클릭한 상황에서, user가 스크롤을 하지 못하도록 막아 두는 것을 많이 볼 수 있습니다. (LINER Web Home 같은 경우에는 이 경우만 overflow-y: hidden 으로 설정해 줬습니다.) 그러한 경우, center-aligned 된 body에서는 스크롤이 원래 차지하고 있던 너비가 줄어들면서, 이렇게 ‘덜컹’거리는 현상이 발생합니다.

물론, 컴퓨터에서 ‘스크롤 자동 숨기기’ 기능을 설정한 user들에게는 이 문제가 나타나지 않습니다. 스크롤을 하고 있지 않을 때는 애초에 스크롤이 차지하고 있는 너비가 없기 때문입니다. 그래서 이 또한, 제가 맥북으로만 개발 및 테스팅을 하고 있었기에 인지하고 있지 못했습니다. (Mac은 디폴트 설정값으로 스크롤 자동 숨기기 기능을 지원합니다.)

처음에는 간단하게 해결할 수 있는 문제라고 여겼지만, os나, browser에 따른 차이들 때문에 상당히 애를 먹었던 과제였습니다.

How? (문제 해결)


1.Cross Browser Scrollbar Styling

모든 브라우저의 style을 동일하게 바꿀 수 있다면 정말 좋겠지만 브라우저마다 css를 다르게 먹여줘야 하는 상황이 생깁니다. HTML/CSS web browser rendering engine이 다르기 때문입니다.

대표적으로 WebKit 은 엔진을 쓰는 Safari/Chrome이 있고 (엄밀히 말하자면 Chrome과 Opera는 Blink 엔진를 사용하고 있는데 Blink는 webkit을 fork 한 것입니다), Firefox 의 경우에는 Gecko 엔진를 사용하고 있는데, LINER가 지원하는 browser는 Chrome, Whale, Opera, Safari, Edge, Firefox 가 있기 때문에 이 6 가지의 브라우저만을 고려하였습니다.

다행히 Scrollbar의 경우 1. Webkit 엔진 browser 와 2. Gecko 엔진 browser (Firefox) 인 두 가지 경우만 다르게 대응을 해주면 됩니다.

Webkit Engine Browsers

::-webkit-scrollbar
::-webkit-scrollbar-button
::-webkit-scrollbar-button:start
::-webkit-scrollbar-button:end
::-webkit-scrollbar-button:vertical:increment
::-webkit-scrollbar-button:vertical:decrement
::-webkit-scrollbar-track
::-webkit-scrollbar-track-piece
::-webkit-scrollbar-thumb
::-webkit-scrollbar-corner
::-webkit-resizer

Firefox (Gecko Engine)

scrollbar-width: thin;
scrollbar-color: gray transparent;

Firefox 의 경우에는 이렇게 두 가지(뿐)의 scroll bar styling option을 적용할 수 있고, scrollbar-width의 경우에는 auto, thin, none 의 선택 가능한 값이 있습니다. (px로는 설정 불가) Firefox는 임의로 개발자가 px로 스크롤의 width를 지정해줄 수 없어서 가장 얇은 두께인 ‘thin’으로만 설정해줄 수 있는데 이것의 width는 약 8px입니다. (이러한 제약 사항 때문에 저는 Firefox와 동일하게 webkit browser들의 scrollbar width도 8px로 맞췄습니다) scrollbar-color의 첫 번째 지정값으로는 Thumb의 색, 후자는 Track의 색을 설정할 수 있습니다.

다시 문제로 돌아와서, Windows의 스크롤 스타일을 MacOS의 스크롤 스타일로 만들기 위해서는 Track의 색을 투명하게 만들어 주어야 하고, scroll track의 border-radius를 줘야 하며, scrollbar의 width를 맞춰줘야 했습니다.

그 결과, webkit engine의 경우에는 다음과 같이 설정했습니다.

/* index.css */

::-webkit-scrollbar {
	width: 8px !important; 
}

::-webkit-scrollbar-thumb {
	border-radius: 8px !important;
	background-color: rgba(75, 75, 75, 0.351) !important;
}

Firefox의 경우에는 아래 코드를 index.css의 body가 아닌

/* index.css */

scrollbar-color: rgba(75, 75, 75, 0.351) rgba(0, 0, 0, 0) !important;
scrollbar-width: thin !important;
:root /* 혹은 */ html{
	scrollbar-color: rgba(75, 75, 75, 0.351) rgba(0, 0, 0, 0) !important;
	scrollbar-width: thin !important;
}

:root 라는 가상 클래스 선택자에 이 scrollbar 지정값을 넣어줬습니다. (:root이 아니라 html tag 에 설정해줘도 됩니다 )

:root 선택자는 문서 구조에서 root 요소 (가장 상위 단계의 부모 요소)에 적용하는데, body가 아닌 그 상위의 요소에 스타일을 주는 것입니다. Scrollbar 의 경우 가장 상위 단계의 부모 요소에 존재하기 때문에 이와 같이 추가해줬습니다.


Firefox 에서는 scroll thumb의 border radius에 대한 설정이나 width의 미세 조정은 따로 해 줄 수 없기 때문에 mac과 동일한 스크롤 스타일을 재현하는 것에 한계가 있습니다.

2. CSS에서 OS 선택자 추가하기

스크롤의 스타일을 정의하는 index.css의 실제 코드는 아래와 같은데 .win 이라는 선택자가 추가로 붙은 것을 확인할 수 있습니다. .win의 win은 windows 를 뜻하는데, MacOS에서 굳이 아래와 같이 스크롤 스타일링을 할 필요가 없기 때문에 (macOS의 경우 아래 코드를 추가하면 스크롤 자동 숨김을 강제로 지원하지 않습니다.) Windows의 경우에만 mac style scroll 을 적용하도록 선택자를 추가해 준 것입니다.

.win:root {
	scrollbar-color: rgba(75, 75, 75, 0.351) rgba(0, 0, 0, 0) !important;
	scrollbar-width: thin !important;
}

.win::-webkit-scrollbar {
	width: 8px !important; 
}

.win::-webkit-scrollbar-thumb {
	border-radius: 8px !important;
	background-color: rgba(75, 75, 75, 0.351) !important;
}

이렇게 OS가 Windows인지, macOS인지 선택자는 기본적으로 CSS에서 지원하고 있지 않습니다. 따라서 이를 위해서는 Javascript를 활용할 수 밖에 없었습니다. React에서는 OS 선택자를 제공해주는 많은 라이브러리들이 있었는데, 필요 이상으로 무거워질 수도 있고 왠지 dependency가 생기는 것 같다는 생각이 들어, 그런 것을 다운 받아서 쓰기보다는 직접 script tag에 넣어서 사용하는 편이 낫겠다는 판단이 들었습니다.

따라서 index.html에 <script> tag 하나를 추가해서, 이 선택자를 지정해줄 수 있는 minified 코드를 추가했습니다.

<!-- OS detection script for OS-specific-CSS -->
<script type="text/javascript">
  function select_os(u){var user_agent=u.toLowerCase(),isAgent=function(t){return user_agent.indexOf(t)>-1},g='gecko',w='webkit',s='safari',o='opera',m='mobile',h=document.documentElement,result=[(!(/opera|webtv/i.test(user_agent))&&/msie\\s(\\d)/.test(user_agent))?('ie ie'+RegExp.$1):isAgent('firefox/2')?g+' ff2':isAgent('firefox/3.5')?g+' ff3 ff3_5':isAgent('firefox/3.6')?g+' ff3 ff3_6':isAgent('firefox/3')?g+' ff3':isAgent('gecko/')?g:isAgent('opera')?o+(/version\\/(\\d+)/.test(user_agent)?' '+o+RegExp.$1:(/opera(\\s|\\/)(\\d+)/.test(user_agent)?' '+o+RegExp.$2:'')):isAgent('konqueror')?'konqueror':isAgent('blackberry')?m+' blackberry':isAgent('android')?m+' android':isAgent('chrome')?w+' chrome':isAgent('iron')?w+' iron':isAgent('applewebkit/')?w+' '+s+(/version\\/(\\d+)/.test(user_agent)?' '+s+RegExp.$1:''):isAgent('mozilla/')?g:'',isAgent('j2me')?m+' j2me':isAgent('iphone')?m+' iphone':isAgent('ipod')?m+' ipod':isAgent('ipad')?m+' ipad':isAgent('mac')?'mac':isAgent('darwin')?'mac':isAgent('webtv')?'webtv':isAgent('win')?'win'+(isAgent('windows nt 6.0')?' visAgentta':''):isAgent('freebsd')?'freebsd':(isAgent('x11')||isAgent('linux'))?'linux':'','js']; classString = result.join(' '); h.className += ' '+classString; return classString;}; select_os(navigator.userAgent);
</script>

이 script 코드를 뜯어볼 필요는 없지만, 여기서 하는 일은, navigator.userAgent를 받아서 conditional operator로 userAgent가 mac인지, windows인지 (+현재까지는 코드에서 직접 활용하고 있지는 않지만, 브라우저까지) 판단해주고 그에 따른 selector를 활용할 수 있게 해줍니다. 이 script tag를 추가해서 Windows 인 user agent에서만 스크롤바의 스타일링을 바꿀 수 있게 됩니다.

(참고했던 포스트: https://rafael.adm.br/css_browser_selector/)

3. Scroll 자동 숨김 인식하기

마지막으로 해결해야 하는 문제는 scrollbar로 인한 덜컹거림 해소였습니다. 스크롤 방지를 해야 하는 Liner home의 상황들 (Search bar activation, Open modal) 에서 덜컹거림 없이 overflow-y: hidden 을 하는 일이었습니다.

앞서 언급했듯이, 이것을 하기 위해서 고려해야 했던 것은 사용자가 scroll 자동 숨기기 설정이 되어 있는지 아닌지의 여부였습니다.

만일, scroll 자동 숨기기 설정이 되어있다면, overflow-y: hidden 를 해도 창의 너비가 바뀌지 않아서 이를 고려하지 않아도 되지만, 항상 스크롤바가 보이고 있는 사용자라면, 변하는 window의 innerwidth를 고려해서 center축이 달라져서 ui가 덜컹거리지 않도록 해야 하기 때문입니다.

isAlwaysVisibleScrollMode = () => {
		const outer = document.createElement('div');
		outer.style.visibility = 'hidden';
		outer.style.width = '100px';
		document.body.appendChild(outer);
		
		const widthNoScroll = outer.offsetWidth;
		outer.style.overflow = 'scroll';
		
		const inner = document.createElement('div');
		inner.style.width = '100%';
		outer.appendChild(inner);
		
		const widthWithScroll = inner.offsetWidth;
		outer.parentNode.removeChild(outer);
		
		return widthNoScroll - widthWithScroll;
	};

이것을 확인하는 자바스크립트 코드입니다. Scrollbar의 너비를 계산하는 코드를 변형해서 만든 코드인데, 이 코드는 실제로 스크롤 자동 숨김이 설정 되었는지, 아닌지 확인할 수 있습니다.

isAlwaysVisibleScrollMode()의 return value가 0이라면 스크롤이 항상 보이는 것이고, 만일 0보다 크다면 (스크롤의 너비가 return 된다면) 스크롤이 자동 숨김을 지원하는 것입니다.

위 method를 이용해서, 스크롤 자동 숨김일 때와 그렇지 않을 때의 경우를 나누어서 스크롤 방지 기능을 구현했습니다.

lockScroll = () => {
		const body = document.getElementsByTagName('body')[0];
		// Disable scroll but prevent hiding scroll bar -> 덜컹 방지!
		if (!this.isAlwaysVisibleScrollMode()) {
			body.style.overflowY = 'hidden';
		}
		else {
			body.style.position = 'absolute';
			body.style.margin = '0 auto';
			body.style.right = '8px';
			body.style.left = 0;
			body.style.overflowY = 'hidden';
		}
	};

만일 자동 숨김 스크롤인 경우 (if 문 안의 조건) overflowY를 그냥 hidden으로 처리하도록 하였고, 스크롤이 항상 보인다면, 임의로 스크롤의 너비만큼 (스크롤의 너비는 모든 브라우저 8px로 통일) 직접 position을 조절해서 ‘덜컹’거리는 현상을 없애 줬습니다.

So What? (결론)


Frontend 개발이 사용자의 접점에 있는 일인 만큼, 고려해야 할 요소들이 정말 많다는 것을 새삼 체감하고 있습니다. Browser 별로, OS 별로 예상했던 결과가 다르게 나오는 것이 가끔 두통을 일으키긴 하지만 그래서 오히려 저에겐 더 흥미롭게 다가왔던 것 같습니다.

고치는데 1시간도 안 걸릴 것이라고 크나큰 착각을 했던 cross-browser & cross-OS scroll 대응을 해보면서 얻은 교훈이 있다면,

  1. Testing 및 QA는 여러 컴퓨터, 여러 브라우저에서 끊임없이 해 볼 것.
  2. 이런 브라우저 간의, 그리고 OS간의 차이들에 대해서 더 공부를 많이 해야 한다는 것 이었습니다.

더 크고 많은 문제들을 발견하고 그런 문제들을 해결하는 날까지 열심히 하겠다는 다짐으로 이번 포스트 마치겠습니다! 🙂