glass-style로 만들었더니 모바일이 20fps가 됐다
배경에 무심코 넣은 blur 한 줄이 프레임을 다 잡아먹고 있었다
#성능최적화 #CSS #backdrop-filter #모바일 #React
glass-style로 만들었더니 모바일이 20fps가 됐다 포트폴리오를 glass-style로 갈아엎었다. 애플이 쓰는 그 반투명 유리 느낌 말이다. 카드 뒤가 살짝 비치고, 화면 뒤에서는 오로라 같은 색 덩어리가 천천히 흘러다니게 해뒀다. 노트북에서 보면 꽤 마음에 들었다. 문제는 폰이었다. 메뉴를 누르면 화면이 한 박자 늦게 따라오고, 스크롤도 손가락을 따라오지 못했다. 처음엔 그냥 내 폰이 오래돼서 그런가 싶었는데 다른 폰에서도 똑같았다. 느낌으로는 못 고친다 "버벅인다"는 말로는 어디를 손대야 할지 알 수가 없다. 그래서 헤드리스 크롬을 띄워서 화면을 390x844로 잡고 CPU를 4배 느리게 만들었다. 중급 안드로이드폰 정도라고 보면 된다. 그 상태에서 페이지만 열어놓고 아무것도 안 건드린 채로 프레임 간격을 재봤다. await send('Emulation.setDeviceMetricsOverride', { width: 390, height: 844, deviceScaleFactor: 2, mobile: true, }); await send('Emulation.setCPUThrottlingRate', { rate: 4 }); 180프레임을 모아보니 이렇게 나왔다. 60fps로 돌려면 프레임 간격이 16.7ms여야 하는데 50ms가 나왔다. 초당 20장쯤 그리고 있었다는 뜻이다. 손도 안 댔는데 이미 이 상태였으니 스크롤이나 화면 전환이 매끄러울 리가 없었다. 범인은 셋이었다 같은 화면에서 비싼 속성이 걸린 요소를 세어봤더니 전체 221개 중에 backdrop-filter가 16개나 붙어 있었다. 어디에 붙었는지 뽑아보니 화면 전체를 덮는 유리 판이 하나, 프로젝트 카드가 여섯, 카드 우상단에 마우스 올렸을 때 뜨는 배지가 여섯, 그리고 하단 독이랑 버튼이 셋이었다. 배지부터 이상했다. 이건 마우스를 올려야 보이는 거라 평소엔 opacity가 0이다. 그런데 투명한 거랑 없는 건 다르다. opacity가 0이어도 레이어는 그대로 만들어지고 매 프레임 합성된다. 게다가 폰에는 마우스가 없으니 저 여섯 개는 사용자가 평생 볼 일이 없는데도 blur 레이어를 여섯 개 만들고 있었던 거다. 그냥 반투명 배경으로 바꿨다. 진짜 큰 건 따로 있었다. 배경에 깔아둔 색 덩어리 세 개였는데, 코드를 다시 보다가 아차 싶었다. .aurora-blob { width: 46vw; height: 46vw; background: radial-gradient(circle at center, #a893ff 0%, rgba(168,147,255,0) 70%); filter: blur(88px); animation: aurora-drift-1 22s ease-in-out infinite; } 화면 절반만 한 덩어리에 88px짜리 블러를 걸어놓고 22초 내내 움직이게 해뒀다. 브라우저 입장에서는 이게 움직일 때마다 그 넓은 영역을 다시 흐리게 만들어야 한다. 그걸 세 개나 돌리고 있었으니 CPU가 남아날 리가 없었다. 근데 자세히 보면 배경이 이미 radial-gradient다. 가운데서 바깥으로 가면서 투명도가 0이 되게 되어 있으니까 가장자리는 이미 부드럽게 사라진다. 거기에 블러를 한 번 더 건 거였다 . 그래서 filter 줄을 지우고, 대신 그라데이션이 사라지는 지점만 70%에서 78%로 조금 넓혔다. 눈으로는 차이를 모르겠는데 계산량은 완전히 달라졌다. 마지막은 카드였다. backdrop-filter는 뒤에 있는 걸 가져다가 흐리게 만드는 작업이라 카드가 여섯 개면 그 일이 여섯 번 일어난다. 노트북은 버티는데 폰 GPU에는 무리였다. 여기서 좀 고민했다. 유리를 전부 걷어내면 애초에 이렇게 만든 이유가 없어지고, 그대로 두면 폰에서 못 쓴다. 결국 개수가 많은 것만 포기하기로 했다. 터치 기기에서는 카드의 blur를 끄고 배경색과 어울리는 불투명한 표면으로 바꿨다. @media (hover: none), (max-width: 640px) { .glass-card, .card { backdrop-filter: none; background: var(--card-solid); } .glass-sheen::before { display: none; } .aurora-blob { animation: none; } } 화면 전체를 덮는 판 하나랑 독, 버튼 세 개는 그대로 유리로 뒀다. 개수가 적어서 비용이 크지 않기도 하고, 생각해보면 유리처럼 보여야 하는 건 떠 있는 것들이지 바닥에 깔린 카드가 아니었다. 카드는 불투명해져도 배경 색이 뒤에서 비쳐 보여서 분위기는 그대로 남았다. 결과 다시 재보니 backdrop-filter가 16개에서 4개로 줄었고 blur 필터는 아예 없어졌다. 프레임 간격은 중앙값 16.7ms, p95도 16.7ms가 나왔다. 32ms를 넘긴 프레임은 하나도 없었다. 20fps에서 60fps가 된 거다. 메뉴를 눌러서 화면이 바뀌는 동안에도 재봤는데 중앙값은 똑같이 16.7ms였고 32ms를 넘긴 건 딱 한 프레임이었다. 그건 새 화면을 처음 그리는 순간이라 어쩔 수 없다. 폰에서는 이렇게 보인다. 카드가 불투명해졌지만 배경 색조는 그대로다. 노트북은 원래대로 유리 그대로 남아 있다. 돌아보면 셋 중에 내가 미리 짐작했던 건 카드뿐이었다. 개수가 많으니까 비싸겠거니 했다. 정작 제일 큰 건 배경에 무심코 넣은 filter 한 줄이었고, 그것도 없어도 되는 줄이었다. 화면에서 제일 눈에 안 띄는 요소가 제일 비쌌던 셈이다. opacity가 0이어도 레이어는 살아 있다는 것도 알고는 있었는데, 내 코드에 그런 게 여섯 개나 있다는 건 세어보기 전까지 몰랐다. 노트북 화면은 아직 카드마다 blur가 걸려 있어서 카드가 스무 개쯤 늘어나면 같은 일이 생길 텐데, 그때 가서 또 재보려고 한다.