애니메이션 Favicon 실전: 브라우저 탭을 움직이게 만들기 (라이브 데모 포함)

Favicon.im

이 글을 읽는 동안, 탭 위에서 뭔가 움직이는 게 눈에 띄었을 겁니다. 그게 애니메이션 favicon입니다. Gmail이 새 메일 개수를 보여줄 때 쓰고, Discord가 알림 점에 쓰는 것과 원리는 똑같습니다. canvas에 그릴 수 있는 거라면 거의 뭐든 됩니다.

이 글에는 직접 만질 수 있는 데모가 들어 있습니다. 버튼을 누르면 실제 브라우저 탭이 변합니다. 스크린샷도, 임베드 영상도 아닙니다. 지금 보고 있는 favicon 자체가 데모입니다.

왜 favicon에 애니메이션을 넣을까?

솔직히 말해, 대부분의 사이트는 안 넣는 게 낫습니다. 모든 탭에서 도는 아이콘은 금방 거슬리고, CPU도 잡아먹습니다. 그래도 진짜 도움이 되는 경우가 몇 가지 있습니다:

  • 로딩이나 처리 상태. 시간이 오래 걸리는 업로드, 내보내기, 빌드. 사용자가 다른 탭으로 옮겨 가서 기다리는 동안, 애니메이션 favicon이 "아직 작업 중이에요"라고 알려줍니다.
  • 알림 배지. 새 메시지, 멘션, 알람. 가볍게 맥동하는 빨간 점이 정적인 점보다 더 잘 보입니다.
  • 실시간 데이터. 트레이딩 대시보드, 모니터링 도구, 스포츠 점수. 탭 제목만으로는 부족할 때.
  • 브랜드 모먼트. 시즌 한정, 출시 당일 축하 같은 것. 자제해서 쓰세요.

여기에 해당하지 않는다면 애니메이션은 빼는 게 낫습니다. 잘 만든 정적 SVG favicon이 파일 크기, 다크 모드, 배터리에서 모두 더 낫습니다.

라이브 데모: 지금 바로 시도하기

애니메이션을 하나 골라 보세요. 그리고 브라우저 탭을 보세요. 위에 있는 작은 아이콘이 실시간으로 다시 그려지고 있습니다.

8배 확대 미리보기

실제 favicon은 16×16 픽셀이라 디테일을 보기 어려워, 왼쪽 박스는 같은 canvas를 최근접 이웃 보간으로 8배 확대해 동시에 보여 줍니다.

상태: 대기 중

이제 애니메이션 루프 전체가 Web Worker 안에서 돌아가는데, 이는 Aymkdn 라이브러리의 favicon_worker.js와 같은 방식입니다. 20ms마다 (50fps), worker가 자기 OffscreenCanvas에 한 프레임을 그리고, convertToBlob + FileReader로 data URL로 내보낸 다음, 그 문자열을 메인 스레드로 다시 보냅니다. 메인 스레드는 그 문자열을 faviconLink.href에 대입할 뿐이고요. 이게 GitHub 데모만큼 부드럽게 움직이는 이유입니다.

실제 서비스에서 어떻게 쓰이는지 보고 싶다면, Random Picker Wheel이 돌아가는 휠과 favicon을 동기화해서 보여줍니다. 회전식 UI는 움직이는 아이콘이 제품에 정말로 어울리는 드문 경우 중 하나죠. 페이지를 열고 휠을 돌려보면 탭 아이콘도 함께 도는 걸 확인할 수 있습니다.

실제로 어떻게 동작하는가

기술 자체는 딱 세 단계입니다:

// 1. favicon link 요소를 가져오거나 새로 만들기
let link = document.querySelector('link[rel~="icon"]');
if (!link) {
  link = document.createElement('link');
  link.rel = 'icon';
  document.head.appendChild(link);
}

// 2. 숨겨진 canvas에 한 프레임 그리기
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');

function drawFrame(t) {
  const scale = 0.5 + 0.5 * Math.abs(Math.sin(t / 400));
  ctx.clearRect(0, 0, 32, 32);
  ctx.fillStyle = '#ef4444';
  ctx.beginPath();
  ctx.arc(16, 16, 14 * scale, 0, Math.PI * 2);
  ctx.fill();

  // 3. canvas를 data URL로 내보내고 favicon에 대입
  link.href = canvas.toDataURL('image/png');

  requestAnimationFrame(drawFrame);
}

requestAnimationFrame(drawFrame);

대략 15줄이면 맥동하는 빨간 점 하나가 완성됩니다. 더 화려한 것들은 결국 canvas에 다른 도형을 그리는 일에 불과합니다.

브라우저 호환성 현실

여기서부터는 좀 답답합니다. 브라우저마다 favicon을 얼마나 부지런히 갱신하는지 차이가 큽니다:

  • Firefox: 탭이 비활성 상태여도 부드럽게 동작합니다. 기준점이라 할 만한 동작.
  • Chrome / Edge: 탭이 활성일 때는 정상. 다른 탭으로 옮겨가면 requestAnimationFrame이 약 1초에 1번 정도로 줄어들어 애니메이션이 느려지거나 멈춥니다.
  • Safari: 활성 상태에서는 동작하지만, 갱신 간격이 길어질 때가 있어 부드러움은 기대하기 어렵습니다.

다행히 가장 흔한 사용 사례 —— 알림 점, 진행 상태 —— 는 어차피 매초 여러 번 갱신할 필요가 없으니 큰 문제는 아닙니다. 60fps의 부드러운 스피너는 대부분 그저 장식일 뿐입니다.

백그라운드 탭을 위한 Web Worker 트릭

위의 "GitHub 스타일 플립" 버튼은 Aymkdn/animated-favicon 라이브러리의 시그니처 애니메이션을 그대로 옮긴 것입니다: 아이콘 A를 3초간 표시, cosine으로 폭을 0까지 줄이고, 아이콘 B로 전환, 다시 펼치기. 뒤의 수식은 라이브러리의 favicon_worker.js에서 그대로 가져온 것입니다: width = canvas.width * Math.abs(Math.cos(progress * Math.PI)). progress가 0.5를 넘으면 두 번째 이미지가 인계받습니다.

Aymkdn 라이브러리는 한 걸음 더 나갔습니다. 이 루프를 Web Worker 안에서 돌립니다. Worker는 탭이 백그라운드로 가도 throttling 되지 않으므로 애니메이션이 계속 동작하고, OffscreenCanvas 덕분에 DOM을 건드리지 않고도 프레임을 그릴 수 있습니다.

대략 이런 패턴입니다:

// 메인 스레드
const worker = new Worker('favicon-worker.js');
worker.onmessage = (e) => {
  if (e.data.type === 'updateFavicon') {
    document.querySelector('link[rel~="icon"]').href = e.data.dataUrl;
  }
};
worker.postMessage({ type: 'init', images: ['icon-a.png', 'icon-b.png'] });

// favicon-worker.js 안에서
const canvas = new OffscreenCanvas(16, 16);
const ctx = canvas.getContext('2d');
// ...한 프레임 그리기...
const blob = await canvas.convertToBlob();
const reader = new FileReader();
reader.onloadend = () => self.postMessage({ type: 'updateFavicon', dataUrl: reader.result });
reader.readAsDataURL(blob);

사용자가 백그라운드에 켜 둔 채로 두는 종류의 앱 —— 채팅 클라이언트, 빌드 대시보드, 모니터링 도구 —— 라면 할 만합니다. 그 외에는 메인 스레드 버전이 더 단순하고 충분합니다.

알아 둘 만한 몇 가지

16×16 또는 32×32로 만들고 그 이상은 쓰지 마세요. 어차피 표시되는 크기는 작으니 큰 canvas는 data URL을 키우고 프레임당 CPU만 더 잡아먹습니다. 32×32에 또렷한 픽셀이 적당한 지점입니다.

PNG로 내보내고 ICO는 쓰지 마세요. 안정적으로 동작하는 건 canvas.toDataURL('image/png')뿐입니다. ICO를 직접 인코딩하려고 하지 마세요.

멈출 때는 원래 favicon을 복원하세요. 애니메이션 시작 전에 link.href를 저장해 두고, 작업이 끝났거나 beforeunload에서 되돌립니다. 반쯤 망가진 애니메이션이 계속 남아 있으면 버그처럼 보입니다.

무한히 돌리지 마세요. 약한 펄스라도 모바일에서는 배터리를 먹습니다. 로딩이 끝났을 때, 사용자가 알림을 읽었을 때, 탭이 포커스를 잃었을 때 멈추세요.

간단한 경우는 정적 이미지로 충분합니다. "읽지 않은 메시지 1개"만 보여주려면, favicon을 빨간 점이 있는 정적 버전으로 바꾸면 됩니다. 애니메이션 필요 없습니다. 사람들이 애니메이션 favicon으로 하는 대부분의 일은 사실 과한 경우가 많습니다.

언제 도입할 만한가

애니메이션 favicon이 잘 어울리는 경우:

  • 애니메이션이 사용자가 실제로 신경 쓰는 상태(업로드, 처리, 새 메시지)를 반영할 때
  • 페이지가 백그라운드 탭에 자주 머무르는 종류일 때
  • 정적 배지나 탭 제목 변경만으로는 같은 의미를 전달할 수 없을 때

잘 안 어울리는 경우:

  • 그냥 장식일 때
  • 탭이 열려 있는 동안 내내 돌아갈 때
  • 주요 사용자가 Safari나 모바일이라 거의 동작하지 않을 때

위 데모를 가져가서 색과 도형을 자기 브랜드에 맞게 바꾸고 출시하세요. 전체 소스는 이 페이지 안에 있습니다. 소스 보기로 복사해 가시면 됩니다.

참고 자료

  1. Aymkdn/animated-favicon (GitHub) —— Web Worker 기반의 애니메이션 favicon 라이브러리. 비활성 탭에서도 계속 동작
  2. The Making of an Animated Favicon —— CSS-Tricks —— Chris Coyier가 풀어 쓴 canvas-to-favicon 기법
  3. How to animate a favicon? —— Stack Overflow —— 다양한 접근 방식과 호환성 메모가 모인 클래식 스레드
  4. OffscreenCanvas —— MDN —— Web Worker 기반 애니메이션 favicon을 가능하게 하는 핵심 API
파비콘 확인하기

favicon.im을 사용하여 파비콘이 올바르게 구성되어 있는지 빠르게 확인하세요. 저희 무료 도구는 모든 브라우저와 기기에서 웹사이트의 파비콘이 올바르게 표시되도록 보장합니다.

무료 공개 서비스

Favicon.im은 전 세계 개발자들이 신뢰하는 완전 무료 공개 서비스입니다.

15M+
월간 파비콘 요청
100%
영구 무료