RETROSPECTIVE · 회고
모바일에서 캔버스 게임이 지지직거린 이유: desynchronized 한 줄
Frontend
리듬게임을 개편하고 휴대폰으로 쳐 보니 노트가 내려오는 화면 전체가 찢어지고 깜빡였다. PC에서는 멀쩡했고, 게임 로직이나 판정에는 아무 문제가 없었다.
원인 1: 입력 지연을 줄이려고 켠 옵션
개편하면서 캔버스를 getContext("2d", { desynchronized: true })로 만들었다. 브라우저 합성(compositor)을 거치지 않고 바로 화면에 그려서 지연을 한 프레임쯤 줄여 주는 옵션이다. 대신 화면 갱신과 동기화되지 않기 때문에, 특히 안드로이드에서는 그리는 도중의 프레임이 그대로 보이면서 찢어짐(tearing)과 깜빡임이 생긴다.
원인 2: 주소창이 움직일 때마다 캔버스 재생성
모바일 브라우저는 주소창이 들어가고 나올 때마다 resize 이벤트를 보낸다. 리사이즈 핸들러가 크기가 같아도 매번 canvas.width를 다시 넣고 흐린 배경을 새로 그리고 있었다. 캔버스 크기를 다시 넣으면 내용이 지워지기 때문에 그 순간 한 프레임이 비고, 블러 계산 때문에 프레임도 밀렸다.
해결
// desynchronized 제거, 배경을 항상 다 칠하니 alpha도 끔
const g = canvas.getContext("2d", { alpha: false })!;
const resize = () => {
const nDpr = Math.min(2, window.devicePixelRatio || 1);
const nCW = Math.min(wrap.clientWidth, 1100);
const nH = Math.max(420, Math.min(window.innerHeight - 170, 760));
// 크기가 그대로면 캔버스를 다시 만들지 않음
if (nDpr === dpr && nCW === CW && nH === H) return;
...
};
덤으로 배경 블러도 바꿨다. ctx.filter = "blur()"는 사파리에서 지원되지 않고 모바일에서 무겁다. 커버 이미지를 가로 24px짜리 캔버스에 줄여 그린 뒤 원래 크기로 늘려 그리면 브라우저의 이미지 보간이 그대로 블러 역할을 한다.
배운 점
- 지연을 줄여 준다는 옵션은 대가가 있다. 체감 이득이 확실하지 않으면 기본값이 안전하다.
- 모바일의
resize는 화면 회전만이 아니라 주소창 때문에도 자주 온다. 핸들러는 값이 실제로 바뀌었을 때만 일하게 만든다. - PC 크롬 개발자 도구의 모바일 모드로는 재현되지 않았다. 그래픽 문제는 실기기에서 확인해야 한다.