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. NativeremovePropertyfixes 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 withposition: absolute; opacity: 0and insert the canvas alongside.