Vol. 31

개발일지

잘 돌던 화면이 저사양 태블릿에서 멈추다시피 한 이유

손님이 쓰는 보급형 태블릿에서 키보드가 1~2fps로 끊겼어요. 범인은 예쁘게 보이려고 넣은 backdrop-filter 블러와 클릭 지연이었습니다. 무엇을 덜어내서 다시 가볍게 만들었는지 정리했어요.

2026.06.17 · 발신 우체부J


title: "잘 돌던 화면이 저사양 태블릿에서 멈추다시피 한 이유" date: "2026-06-17" description: "손님이 쓰는 보급형 태블릿에서 키보드가 1~2fps로 끊겼어요. 범인은 예쁘게 보이려고 넣은 backdrop-filter 블러와 클릭 지연이었습니다. 무엇을 덜어내서 다시 가볍게 만들었는지 정리했어요." tags: ["dev", "performance", "css", "frontend"] category: "dev"

제가 만든 작은 웹앱 중에, 손님이 게임 중에 코드를 입력하고 메시지를 주고받는 메신저 형태의 화면이 하나 있어요. 보급형 태블릿에 띄워두고 쓰는 건데, 제 폰이나 PC에서 볼 때는 아무 문제가 없었거든요. 그런데 정작 현장 태블릿에서는 키보드가 거의 멈추다시피 느려져 있었습니다.

키를 한 번 누르면 한 박자 뒤에 그 키에 불이 들어오고, 글자가 입력창에 뜨기까지는 또 한참이 걸렸어요. 체감상 1~2fps. 코드 몇 자리 넣는 것도 고역인 수준이었죠. 오늘은 이 느림의 범인을 어떻게 찾아 덜어냈는지 적어둡니다.

증상부터 — "내 기기에서만 멀쩡"

제일 헷갈렸던 게 이거예요. 개발하는 기기에서는 멀쩡한데 현장 태블릿에서만 느림. 태블릿이 아주 옛날 기기도 아니었어요. 그러니까 "기기가 낡아서"가 아니라, 저사양 환경의 GPU/CPU가 감당 못 할 무언가를 화면이 매 프레임 시키고 있다는 쪽으로 의심해야 했습니다.

손이 가장 많이 닿는 곳이 키보드라, 키를 누를 때 화면이 뭘 다시 그리는지부터 봤어요.

범인 1 — 키 누를 때마다 다시 계산되는 블러

키보드가 들어 있는 입력창(composer)과 상단 헤더에 이런 게 걸려 있었어요.

.composer, .msg-header {
  backdrop-filter: blur(4px);   /* 뒤 배경을 흐리게 — 예쁘라고 넣은 것 */
}

backdrop-filter요소 뒤의 픽셀을 실시간으로 흐리게 만드는 효과예요. 문제는 키를 누를 때마다 키에 :active 스타일이 들어가면서 그 영역이 다시 그려지고, 그때마다 블러를 다시 계산한다는 거였어요. 데스크톱 GPU는 이걸 가볍게 넘기지만, 보급형 태블릿에서는 이 재계산 하나가 프레임을 통째로 잡아먹고 있었습니다.

해결은 단순했어요. 블러를 걷어내고, 대신 배경색 투명도를 거의 불투명하게 올려서 가독성은 그대로 유지했어요.

:root {
  --header-bg: rgba(0, 16, 8, 0.94);  /* 0.78 → 0.94, 블러 없이도 글씨가 잘 보이게 */
}
.composer, .msg-header {
  /* backdrop-filter 제거 */
}

블러 한 줄을 빼는 것만으로 키 입력 체감이 확 살아났어요. 예쁘라고 넣은 효과가 정작 손님이 가장 많이 만지는 곳을 망치고 있었던 거죠.

범인 2 — click의 300ms 지연

또 하나, 키 입력을 click 이벤트로 받고 있었어요. 모바일 브라우저에서 click은 더블탭 확대 같은 걸 구분하느라 약 300ms 늦게 발생할 수 있어요. 터치는 했는데 반응은 한 박자 늦는 그 느낌의 일부가 여기서 왔습니다.

그래서 pointerdown으로 바꿨어요. 손가락이 닿는 즉시 발생하니까 지연이 없어요.

// before: click 위임 — 모바일에서 최대 300ms 지연
kbd.addEventListener("click", handleKey);

// after: pointerdown — 터치 즉시 반응
kbd.addEventListener("pointerdown", (e) => {
  const key = e.target.closest(".kbd-key");
  if (key) e.preventDefault();   // 포커스 이동·고스트 click 방지
  if (!key) return;
  handleKey(key);
});

preventDefault()로 뒤따라오는 고스트 click과 포커스 이동까지 막아주면 깔끔해요. 입력창이 어차피 읽기 전용이라 blur가 동작에 영향을 주지 않는 구조라 가능했어요.

마무리 청소 — 상시 애니메이션과 무거운 이미지

여기까지로 키보드는 살아났고, 내친김에 늘 돌고 있던 것들도 정리했어요.

  • 상시 애니메이션 제거: 대기 화면에서 계속 번지던 드롭섀도 애니메이션, 접속 표시 점이 깜빡이던 효과를 뺐어요. 가만히 있어도 매 프레임 일을 시키던 것들이라, 빼는 것만으로 기기가 한가해졌어요.
  • 화면 밖 메시지 렌더 스킵: 지난 메시지 묶음에 content-visibility: auto를 줘서, 스크롤 밖으로 나간 말풍선은 아예 그리지 않게 했어요.
.bubble-group {
  content-visibility: auto;   /* 화면 밖이면 렌더 건너뜀 */
}
  • 첨부 이미지 JPEG화: 힌트로 띄우던 PNG들이 1.4MB가 넘었어요. 화질 거의 그대로(q90) JPEG로 바꾸니 179KB까지 떨어졌어요. 저사양 기기에서 큰 PNG를 디코딩하는 비용도 만만치 않거든요.

남는 생각

이번에 제일 크게 느낀 건, 개발하는 기기에서 잘 돌아가는 건 아무 의미가 없다는 거예요. 실제로 쓰이는 그 기기, 그 환경에서 돌려봐야 진짜를 알 수 있더라고요. 그리고 성능 문제는 보통 "뭘 더 넣어서"가 아니라 "뭘 덜어내서" 풀리는 경우가 많았어요. 예쁘라고 넣은 효과 한 줄이 손님 경험을 다 깎아먹고 있었으니까요.

다음에 뭘 만들 땐 처음부터 가장 약한 기기를 기준으로 잡고 시작하려고요. 거기서 잘 돌면, 좋은 기기에서는 알아서 잘 도니까요.