博客 Logo 动画是怎么做的

网站左上角那个小方框, 是视频.

默认灰度, hover 才亮起来, 颜色每次还不一样, 播放速度也会先快后缓. 像被惊醒了一下.

为什么做这个

之前 logo 是张静态 PNG. 总觉得缺了点呼吸感. 正好手头有一段素材, 就把 logo 从静态图换成了视频.

mp4 没有 Alpha 通道

剪映导出时明明去掉了背景, 但 mp4 不支持透明通道, 透明部分被压成了黑色.

桌面端用 mix-blend-mode: screen 解决 — 黑色变完全透明, 只保留亮色部分:

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

限定 (hover: hover) 是因为移动端浏览器对 <video> 的 blend mode 支持普遍不好.

移动端改用 <canvas> 做逐像素抠图: video 隐藏继续播放, canvas 每帧取画面, RGB 都低于 30 的像素 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);
}

这个性能取决于视频分辨率, logo 尺寸很小, 所以开销可以接受.

hover 三件事

鼠标移入 logo 时三件事同时发生:

1. 滤镜随机变色. 准备一组 CSS filter 组合, 每次随机挑一个应用, 离开时 removeProperty 恢复灰度.

2. 弹性放大. transform: scale(1.15) 用 cubic-bezier(0.34, 1.56, 0.64, 1) 过渡. 这个曲线的 y1 > 1, 动画会先冲过头再弹回来, 像被戳了一下.

3. 播放速度先快后缓. 默认 0.9x, hover 瞬间提到 3x, 然后 3 秒内平滑减速回到 0.9x. 用 requestAnimationFrame 逐帧调 playbackRate, 缓出曲线让减速自然. 这是唯一必须用 JS 的部分 — playbackRate 没有 CSS 属性.

transition 是核心

三个效果里两个靠 transition 驱动: 告诉浏览器属性变化时不要跳变, 在一段时间内平滑过渡. filter 和 transform 都是 GPU 加速的, 不触发重排.

移动端交互

移动端没有 hover, 用 touchstart 触发同样的动画, 4 秒后自动恢复:

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

动画逻辑共用 applyAnim / resetAnim 函数, 通过 displayEl 操作 CSS, 通过 videoEl 调播放速度.

  • pointer-events: video 会拦截点击, 尝试过加 pointer-events: none, 但鼠标事件也收不到了. 后来发现根本不需要 — 把 <a> 的 href 去掉就行.
  • filter 过渡不掉: $().css('filter', '') 清除 inline 样式时有时过渡不触发. 改用原生 removeProperty 解决.
  • 导航 hover 干扰: 主题的 #header:hover #logo { filter: none } 太宽泛, 导航悬停时 logo 也失了灰度. 用 #header:hover #logo { filter: grayscale(100%) } 锁住.
  • canvas 替换 video: 不能用 replaceChild, video 会停止播放. 改用 position: absolute; opacity: 0 隐藏, canvas 插入到同一位置.

文章链接:

/zh/archive/blog-architecture-animated-logo/

# 相关文章推荐