새로고침마다 깜빡이는 그래프
브라우저가 스크립트 실행 전에 첫 화면을 그리기 때문이며, 그래프 크기를 미리 알면 정적 그림으로 같은 모습을 먼저 보여 주고 크기가 화면마다 다르면 스크립트가 끝날 때까지 렌더링을 미룬다.
목차
생각의 정원의 홈과 생각 지도에는 스크립트가 그리는 SVG 그래프가 있다. 새로고침하면 그래프가 잠깐 다른 모습으로 보인 뒤 다시 그려졌다. 홈에서는 큰 글자가 작은 글자로 줄어들고, 생각 지도에서는 비어 있던 그래프 영역이 채워졌다. 느린 게 아니라 보이는 것이 바뀌는 게 문제였다.
문제는 속도가 아니라 첫 화면의 내용
브라우저는 모듈 스크립트의 실행을 기다리지 않고 첫 화면을 그린다. 캐시가 만료됐거나 재검증이 필요하면 새로고침 때 스크립트를 서버에 다시 확인하는 왕복도 생긴다. 처음 그린 화면에는 홈의 정적 그림과 생각 지도의 빈 <svg>가 보였다. 모바일 홈에서는 글자를 크게 잡은 그림이 먼저 보인 뒤, 스크립트가 실행되며 제목 크기와 배치가 달라졌다. 데이터 요청을 없애 모듈 스크립트가 실행되는 시점까지 앞당긴 뒤에도 첫 그리기가 조금 앞서 짧은 빈 프레임이 남았다. 빠르게 그리는 것과 첫 화면을 채우는 것은 다른 목표다.
왕복 줄이기: 데이터를 페이지에 넣고 의존 파일 요청 앞당기기
그래도 네트워크 요청은 줄여야 했다. 노드와 간선을 페이지에 <script type="application/json">으로 넣었다. 홈은 빌드 때 계산한 좌표까지 싣고, 생각 지도는 선택한 노트의 제목·요약·참조·역참조 정보까지 싣는다. 필요한 필드만 넣고 반복되는 경로가 많은 JSON으로 만들어, gzip으로 압축했을 때 전송량도 작아지게 했다. 페이지 전체 데이터를 받거나 별도 요청을 보내는 것보다 전송량이 작다. 이 정보를 뒤에서 받게 두면 제목을 먼저 그리고 요약과 목록을 나중에 끼워 넣으며 화면이 흔들린다. 두 단계 렌더링은 그 자체가 깜빡임이다.
이 프로젝트의 현재 Astro 빌드에서는 페이지 스크립트가 불러오는 청크(분할된 JavaScript 파일)에 modulepreload를 자동으로 넣지 않았다. 브라우저가 페이지 스크립트를 받은 뒤 정적 import를 따라 청크를 알게 되므로, 의존 파일 요청이 늦어진다. 빌드 산출물의 정적 import를 따라 <link rel="modulepreload">를 넣어 의존 파일 요청을 앞당겼다.
홈: 미리 그린 그림으로 교체를 가리기
홈은 빌드 때 그래프 좌표를 계산하고, 데스크톱과 모바일의 그래프 영역에 맞는 정적 그림을 미리 만든다. 일반적인 데스크톱 화면에서는 그래프 영역의 세로 크기가 스냅샷을 만든 기준과 같아 배율도 예측할 수 있다. 배치를 시작할 위치를 정하는 초기값을 고정해 같은 노드·간선·배치에 사용하는 좌표 공간의 크기·설정에서 같은 좌표가 나오게 했다. 배치 함수는 외부 상태에 의존하지 않는 함수로 분리했다. 빌드 때 계산한 좌표를 스냅샷과 브라우저 그래프에 함께 넣어, 두 그림이 같은 위치에서 시작하게 했다. 노드 제목을 겹치지 않게 놓는 규칙은 그래프의 노드 제목이 겹치지 않게 놓는 방법에서 다룬다.
노드 좌표의 최소·최대 범위에 엔진과 같은 여백을 더한 영역을 viewBox(SVG가 그릴 좌표 영역)로 썼다. SVG의 meet 방식(그림 전체가 화면 영역 안에 들어오도록 맞추는 방식)이 이 좌표 영역을 SVG를 표시하는 영역에 맞추므로, 일반적인 데스크톱에서는 엔진과 스냅샷의 배율과 위치가 맞는다. 화면에서 크기를 유지해야 하는 제목과 영역 이름은 엔진의 화면 크기 값에 배율의 역수를 곱해 SVG 내부 좌표로 적었다. 선과 노드는 그림과 함께 확대·축소되므로 엔진의 장면 좌표를 그대로 썼다.
일반적인 데스크톱에서는 좌표와 맞춤 규칙이 같아, 스냅샷이 살아 있는 그래프로 바뀌는 순간을 눈으로 구분하기 어렵다.
크기가 바뀌는 생각 지도: 렌더링 차단
생각 지도의 그래프는 그래프 영역의 크기가 화면마다 달라 배율을 빌드 때 정할 수 없고, 같은 스냅샷을 모든 화면에 쓸 수 없다. 대신 렌더링 순서를 바꿨다. 지원하는 브라우저에서 <head> 안의 스크립트에 blocking="render"를 달면 그 스크립트를 렌더링을 막는 대상으로 취급한다. 그래프와 노트 정보를 그린 뒤 화면이 나오도록 해, 빈 영역이 먼저 보이는 구간을 없애는 방식이다. 이 프로젝트에서는 소스의 <script>가 아니라, 빌드 뒤 HTML에서 페이지 스크립트를 <head>로 옮기고 속성을 달았다.
비용은 그래프 스크립트와 그 의존 파일이 준비되고 실행될 때까지의 첫 화면 지연이다. 그래프가 곧 내용인 페이지라 이 비용을 감수했고, 스냅샷이 자리를 채우는 홈에는 적용하지 않았다.
확인 범위
blocking="render"의 브라우저별 지원 범위는 이 작업에서 따로 검증하지 않았다. 지원하지 않는 브라우저에서는 속성이 렌더링 차단으로 적용되지 않아, 스크립트 실행 전 빈 프레임이 보일 수 있다. 호스팅의 캐시 정책에 따라 캐시 재검증에 걸리는 시간은 이 페이지 코드만으로 줄일 수 없다. 데스크톱 스냅샷은 500px 높이를 기준으로 만들었기 때문에, 아주 낮은 창에서 실제 홈 그래프 영역이 그보다 작아지면 스냅샷과 실시간 그래프의 배율이 어긋날 수 있다.