잘 돌던 화면이 저사양 태블릿에서 멈추다시피 한 이유
손님이 쓰는 보급형 태블릿에서 키보드가 1~2fps로 끊겼어요. 범인은 예쁘게 보이려고 넣은 backdrop-filter 블러와 클릭 지연이었습니다. 무엇을 덜어내서 다시 가볍게 만들었는지 정리했어요.
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를 디코딩하는 비용도 만만치 않거든요.
남는 생각
이번에 제일 크게 느낀 건, 개발하는 기기에서 잘 돌아가는 건 아무 의미가 없다는 거예요. 실제로 쓰이는 그 기기, 그 환경에서 돌려봐야 진짜를 알 수 있더라고요. 그리고 성능 문제는 보통 "뭘 더 넣어서"가 아니라 "뭘 덜어내서" 풀리는 경우가 많았어요. 예쁘라고 넣은 효과 한 줄이 손님 경험을 다 깎아먹고 있었으니까요.
다음에 뭘 만들 땐 처음부터 가장 약한 기기를 기준으로 잡고 시작하려고요. 거기서 잘 돌면, 좋은 기기에서는 알아서 잘 도니까요.