[실전 성능 04] 흔들리지 않는 화면

지난 편에서 첫 화면을 빨리 띄우는 법을 다뤘다. 그런데 빨리 뜨기만 해서는 부족했다. 떠 있는 화면이 갑자기 밀려서 읽던 글자가 도망가고, 누르려던 버튼이 딴 데로 튀는 경험은 속도만큼이나 사람을 화나게 했다. 이번 편은 시각 안정성, 즉 레이아웃이 흔들리는 문제와 내가 그걸 잡은 과정이다.


1. 레이아웃 이동이란 무엇인가

시각 안정성 지표는 화면에 이미 그려진 요소가 예고 없이 위치를 바꾸는 정도를 잰다. 사람이 글을 읽고 있는데 위쪽에 뭔가 뒤늦게 끼어들면서 본문이 아래로 쭉 밀리는 상황을 떠올리면 된다. 이 밀림이 클수록, 그리고 자주 일어날수록 지표가 나빠진다. 누적된 이동량이 0.1을 넘으면 좋지 않음으로 친다.


이게 왜 나쁜지는 직접 당해보면 안다. 나는 기사 하단의 링크를 누르려는 순간 광고가 로딩되며 화면이 밀려서 엉뚱한 버튼을 누른 적이 많았다. 화가 났고, 그 사이트를 신뢰하지 않게 됐다. 내 사용자도 똑같이 느낄 터였다. 흔들리는 화면은 느린 화면보다 더 배신감을 줬다. 멀쩡하던 게 갑자기 움직이니까. 느린 건 그래도 예상 가능한 불편이지만, 안정적이던 화면이 손을 대는 순간 배신하듯 튀는 경험은 사용자에게 통제권을 빼앗겼다는 불쾌함을 남겼다.


중요한 건 이 지표가 나쁜 이동만 잡는다는 점이었다. 사용자가 버튼을 눌러서 메뉴가 펼쳐지는 것처럼 예상된 움직임은 세지 않는다. 문제가 되는 건 사용자가 원치 않았는데 뒤늦게 도착한 콘텐츠가 밀어내는 경우다. 즉 예고 없는 이동이 핵심이었다. 나는 내 화면에서 예고 없이 움직이는 것들을 하나씩 찾아 나섰다.


범인을 찾는 방법은 화면 녹화였다. 느린 회선으로 페이지를 열고 로딩 과정을 천천히 재생하니, 어느 요소가 뒤늦게 나타나며 아래를 밀어내는지 눈으로 보였다. 대부분 이미지, 광고 자리, 뒤늦게 불러오는 위젯, 그리고 웹폰트가 범인이었다. 원인을 눈으로 확인하니 해결이 훨씬 수월했다. 막연히 화면이 흔들린다고 느낄 때는 막막했는데, 녹화를 프레임 단위로 넘겨보며 어느 요소가 언제 튀어 들어오는지 콕 집으니 고칠 대상이 분명해졌다.


2. 이미지가 만드는 이동

가장 흔한 범인은 이미지였다. 브라우저는 이미지의 실제 크기를 모르면 일단 높이를 0으로 잡고 그 아래 콘텐츠를 그린다. 그러다 이미지가 다운로드되어 크기가 정해지면 그만큼 아래 콘텐츠를 밀어낸다. 이 밀림이 레이아웃 이동의 대표적 원인이었다. 나도 초기에 이걸로 크게 데였다.


해법은 의외로 간단했다. 이미지에 가로와 세로 크기를 미리 알려주면, 브라우저가 다운로드 전에 그만큼 공간을 비워둔다. 이미지가 나중에 도착해도 이미 자리가 예약돼 있으니 아래 콘텐츠가 밀리지 않는다. 나는 모든 이미지에 크기 정보를 넣는 것을 규칙으로 삼았다. 이 작은 습관이 이동량을 크게 줄였다.


반응형에서는 크기가 화면 폭에 따라 달라지니 고정 숫자만으로는 부족했다. 이때는 가로세로 비율을 지정하는 방식이 유용했다. 비율만 알려주면 폭이 어떻게 변하든 브라우저가 높이를 계산해 자리를 잡아뒀다. 나는 목록 썸네일처럼 크기가 유동적인 곳에 이 비율 지정을 적극 썼다. 폭이 달라져도 자리는 흔들리지 않았다.


이 주제는 워낙 자주 겪는 일이라 다음 편을 통째로 이미지 레이아웃 이동에 할애했다. 여기서는 원리만 기억하면 된다. 콘텐츠가 도착하기 전에 그 자리를 미리 비워두면 흔들림이 없다는 것이다. 자리 예약이 시각 안정성의 핵심 전략이었다. 도착 시점은 네트워크에 달려 있어 내가 못 정하지만, 도착할 자리를 미리 잡아두는 것은 온전히 내가 코드로 통제할 수 있는 영역이었다.


3. 늦게 오는 콘텐츠에 자리 비워두기

이미지 말고도 뒤늦게 도착하는 것들이 많았다. 광고 영역, 추천 위젯, 로그인 상태에 따라 달라지는 배너 같은 것들이다. 이런 요소들은 처음엔 비어 있다가 데이터가 오면 채워지는데, 그때 갑자기 공간을 차지하며 아래를 밀었다. 나는 이들에게도 미리 자리를 잡아줬다.


방법은 콘텐츠가 오기 전에도 그만한 크기의 빈 상자를 그려두는 것이었다. 위젯이 들어올 자리에 최소 높이를 정해두면, 실제 콘텐츠가 그 안에 채워져도 바깥 레이아웃은 그대로였다. 빈 상자가 조금 허전해 보여도, 화면이 밀리는 것보다는 훨씬 나았다. 나는 안정성을 위해 약간의 빈 공간을 기꺼이 감수했다.


특히 로그인 여부에 따라 달라지는 영역이 골칫거리였다. 처음엔 비로그인 상태로 그렸다가 로그인 정보가 확인되면 다시 그리면서 화면이 출렁였다. 나는 이런 영역도 높이를 고정해두고, 안의 내용만 바뀌게 해서 바깥이 흔들리지 않도록 했다. 내용은 바뀌어도 뼈대는 고정한다는 원칙이었다.


이 작업의 핵심은 예측이었다. 무엇이 뒤늦게 올지 미리 알고, 그것이 차지할 공간을 앞서 확보해두는 것이다. 늦게 오는 걸 막을 수는 없지만, 그것이 도착할 자리를 비워두는 건 내가 통제할 수 있었다. 통제 가능한 것에 집중하니 이동량이 착실히 줄었다. 무엇이 언제 올지 목록으로 적어두고 각각에 자리를 배정하는 습관을 들이자, 새 위젯을 붙일 때도 반사적으로 자리부터 확보하게 되었다.


4. 폰트가 만드는 흔들림

의외의 범인은 웹폰트였다. 웹폰트를 쓰면 폰트가 다운로드되기 전까지 기본 폰트로 글자를 보여주다가, 폰트가 도착하면 갑자기 그 폰트로 바꿔 그린다. 이때 두 폰트의 글자 폭이 다르면 텍스트 전체 길이가 변하면서 줄바꿈이 달라지고 레이아웃이 출렁였다. 나도 처음엔 이걸 몰라 한참 헤맸다.


내 해법은 두 갈래였다. 우선 데스크탑에서는 시스템에 이미 깔린 폰트를 그대로 썼다. 시스템 폰트는 다운로드가 없으니 교체로 인한 흔들림 자체가 없었다. 모바일에서만 가벼운 웹폰트를 쓰되, 폰트가 오기 전에도 기본 폰트로 즉시 글자를 보여주도록 했다. 빈 화면 대신 일단 읽히게 한 것이다.


폰트 교체로 인한 흔들림을 더 줄이려면, 기본 폰트와 웹폰트의 크기를 맞춰주는 조정도 가능했다. 폴백 폰트의 글자 폭을 웹폰트에 가깝게 보정하면 교체 순간의 길이 변화가 최소화됐다. 이 세밀한 조정까지 하니 폰트로 인한 이동이 거의 사라졌다. 폰트 최적화는 뒤에서 한 편을 통째로 할애해 다룬다.


폰트 문제는 시각 안정성과 로딩이 얽혀 있어 흥미로웠다. 폰트를 늦게 보여주면 흔들림은 줄지만 텍스트가 늦게 뜨고, 빨리 보여주면 텍스트는 빠르지만 교체 흔들림이 생겼다. 나는 시스템 폰트로 이 딜레마 자체를 피하는 쪽을 데스크탑에서 택했다. 문제를 푸는 것보다 문제를 없애는 게 나을 때가 있었다. 웹폰트를 정교하게 다루는 기술을 익히는 것도 방법이지만, 애초에 다운로드가 없는 폰트를 쓰면 교체 흔들림이라는 문제 자체가 성립하지 않았다.


5. 안정성이 주는 신뢰

시각 안정성을 챙기고 나서 느낀 건, 이게 단순히 지표 하나가 아니라 신뢰의 문제라는 점이었다. 화면이 흔들리지 않으면 사용자는 사이트가 견고하다고 느꼈다. 반대로 자꾸 밀리면 어설프고 불안한 사이트라는 인상을 줬다. 속도가 첫인상이라면 안정성은 신뢰였다.


이 지표는 특히 모바일에서 중요했다. 작은 화면에서 손가락으로 누르는데 화면이 밀리면 오조작이 잦았다. 결제나 삭제 같은 중요한 버튼 근처가 밀리면 사고로 이어졌다. 나는 중요한 조작 영역 주변을 특히 단단히 고정했다. 실수로 잘못 누르는 일을 막는 것도 성능의 일부였다. 작은 화면에서 손가락은 마우스보다 둔한 도구라, 목표가 로딩 도중 몇 픽셀만 움직여도 빗나가기 일쑤였고 그 실수의 대가는 종종 되돌리기 어려웠다.


안정성 작업의 좋은 점은 한 번 잡아두면 잘 무너지지 않는다는 것이었다. 자리를 비워두는 습관을 코드에 녹여두니, 새 기능을 추가해도 흔들림이 재발하지 않았다. 로딩이나 반응성은 계속 관리해야 했지만, 안정성은 구조로 해결되는 면이 컸다. 나는 이 지표를 구조적으로 방어하는 데 집중했다. 자리를 비워두는 규칙을 컴포넌트 단위에 못 박아두니, 이후로는 개별 페이지마다 신경 쓰지 않아도 흔들림이 알아서 예방되었다.


이렇게 화면을 안 흔들리게 만들었으니, 이제 남은 건 반응성이다. 화면이 빨리 뜨고 안 흔들려도, 눌렀을 때 반응이 없으면 사용자는 여전히 답답하다. 다음 편에서는 상호작용에 대한 반응, 즉 눌렀을 때 얼마나 빨리 화면이 응답하는지를 다룬다. 요즘 가장 많은 사이트가 걸려 넘어지는 지표이기도 하다.