Adding an Animated Logo to My Blog

That small square in the top-left corner of the site? It’s a video.

Grayscale by default. Lights up on hover — different color every time, and the playback speed surges then eases back down. Like it just woke up.

Why

The old logo was a static PNG. It felt lifeless. I had a short video clip lying around, so I swapped the static image for a video.

mp4 Has No Alpha Channel

CapCut removed the background on export, but mp4 doesn’t support transparency — it gets baked to black.

Desktop: mix-blend-mode: screen makes black fully transparent, keeping only the bright parts:

@media (hover: hover) and (pointer: fine) {
  #header #logo { mix-blend-mode: screen; }
}

Restricted to (hover: hover) because mobile browsers have poor mix-blend-mode support on <video>.

Mobile: render through <canvas> with per-pixel chroma-key. The video stays hidden (opacity: 0) as the source. Canvas processes each frame — pixels with all RGB channels below 30 get alpha = 0:

function render() {
  canvasCtx.drawImage(videoEl, 0, 0, w, h);
  var src = canvasCtx.getImageData(0, 0, w, h).data;
  var id = canvasCtx.createImageData(w, h);
  for (var i = 0; i < src.length; i += 4) {
    id.data[i]   = src[i];
    id.data[i+1] = src[i+1];
    id.data[i+2] = src[i+2];
    id.data[i+3] = (src[i] < 30 && src[i+1] < 30 && src[i+2] < 30) ? 0 : 255;
  }
  canvasCtx.putImageData(id, 0, 0);
  requestAnimationFrame(render);
}

Performance depends on resolution — the logo is small so this is fine.

Three Things on Hover

1. Random color filter. A set of CSS filter presets. Picks one randomly on each hover. On leave, removeProperty restores grayscale.

2. Elastic scale. transform: scale(1.15) with cubic-bezier(0.34, 1.56, 0.64, 1). The y1 > 1 causes overshoot — like getting poked.

3. Speed burst. Default 0.9x. Jumps to 3x on hover, then decelerates back to 0.9x over 3s via requestAnimationFrame. The only part that needs JS — playbackRate has no CSS equivalent.

transition Is the Core

Two of the three effects are driven by CSS transitions. Filter and transform are both GPU-accelerated — no layout or repaint.

Mobile Interaction

No hover on mobile. Touch triggers the same animation with auto-revert after 4s:

$link.on('touchstart', function (e) {
  e.preventDefault();
  applyAnim();
  clearTimeout(animTimer);
  animTimer = setTimeout(resetAnim, 4000);
});

Shared applyAnim / resetAnim functions. CSS goes on displayEl (video or canvas), playback rate goes on videoEl (the real video element).

Pitfalls

  • pointer-events: Videos intercept clicks. Tried pointer-events: none, but it also killed hover events. Didn’t need it — just removed the href from the parent <a>.
  • filter transition on removal: $().css('filter', '') sometimes skips the transition. Native removeProperty fixes it.
  • nav hover interference: The theme’s #header:hover #logo { filter: none } is too broad — hovering the nav also removed grayscale from the logo. Locked it back with #header:hover #logo { filter: grayscale(100%) }.
  • canvas replaces video: Don’t use replaceChild — the video stops playing. Hide it with position: absolute; opacity: 0 and insert the canvas alongside.

Article Link:

/en/archive/blog-architecture-animated-logo/

# Related Articles