Kirby
首页文章随笔书签提示词AI 助手
更多

网站里的小动画

2024-09-06·
次浏览

AI 摘要

生成中...

今天我想和大家分享一下这个网站中的一些动画细节,以及它们背后的实现思路。说来也怪,我做个人博客的时候,明明市面上有一堆现成的方案能让我三两下就搭好一个,看着也挺专业。但我没有,就想完全按自己的想法来折腾。

于是选了 Next.js + Framer Motion 的组合,一路磕磕绊绊到现在。你打开网站随便逛逛,就会发现几乎每个页面都藏着动画。这里点点,那里动动,有些动画看着不起眼,但每个都花了我不少时间去调。就像打磨一件东西,总想让它顺手舒服点。

如果你也对 Framer Motion 感兴趣,我建议别一上来就钻那些复杂的文档。先从基础的 fade in/out 开始练起,慢慢来。

今天就来聊聊这些动画背后的故事吧。

页面内容入场动画

网站每个页面都有个统一的入场效果,内容从上往下依次冒出来。这个想法其实来自生活场景:想象秋风吹过落叶,一片片被轻轻推开;或者工厂里拉下总闸,一排灯从左到右依次亮起。

本来还想加点模糊效果,让内容从模糊到清晰慢慢显现。但试了试发现,在页面加载的时候用太多模糊会卡一下,体验不好就砍掉了。

内容出现的间隔时间最后定在 0.5 秒。这个数字可不是随便选的,调成 1 秒就感觉拖沓得不行,0.2 秒又太快了看不清。反复试了好多次,才觉得 0.5 秒刚刚好。

这个动画现在成了网站的基础效果,不仅页面加载用,还能用到其他地方。核心代码其实挺简单的:

const animationConfig = useCallback(
  () => ({
    initial: { opacity: 0, y: 20 },
    animate: { opacity: 1, y: 0 },
    exit: { opacity: 0, y: 20 },
    transition: { ease: 'easeOut', duration: 0.3, delay },
  }),
  [delay]
)

头像的眼睛会动

最开始头像就是张静态图片,看着挺可爱的。后来网站其他地方动画加得越来越多,突然想到:要不让眼睛也动动?

于是打开 Figma,花了点时间画了这个卡比头像。

Figma中绘制的头像

导出 SVG 代码后,发现代码长得吓人。让 GPT 帮我精简了一下,把小数点保留两位,视觉上没区别但代码短了不少。上网页试了试,觉得蓝色眼袋不够明显就去掉了。

然后加了两个小功能:

  1. 眼睛会跟着鼠标轻微移动,看着像在关注你(不是监视你啊喂)
  2. 每 8 秒眨一次眼。人类平均 5-10 秒眨眼,8 秒我觉得刚刚好,不频繁也不奇怪

眨眼的动画用 CSS clip-path 做的:

@keyframes blink {
  0%,
  100% {
    clip-path: inset(0% 0% 0% 0%);
  }
  50% {
    clip-path: inset(50% 0% 50% 0%);
  }
}
.blink {
  animation: blink 0.2s ease-in-out;
}

音乐播放动画

这个音乐播放区域大概是我花时间最多的地方。最终效果其实是 5 个动画叠在一起。你问我值不值得?我也知道可以简化,但每个小效果都舍不得删。

刚打开首页的时候,唱片封面旁边是个 loading 的骨架屏。最开始这里就是干巴巴的"暂无播放"文字,后来改成骨架屏,看着舒服点,也确实在从 Supabase 拉数据。

等到歌曲信息回来,原来的默认封面往左移淡出,真封面从右往左滑进来。封面消失的时候还有个小细节:先往右微微动一下,再往左走。用的是 Framer Motion 的 anticipate ease,感觉像蓄力后发力。游戏里常见,比如 LOL 里韦鲁斯的技能,先往后拉再射出去。比直接 easeOut 动起来更有预感。

切歌的时候同样的转场再来一遍,还有 1 秒延迟(这里就不细说了)。整个过程还加了透明度、模糊、缩放各种变化。

跳动的音符从 Arc 浏览器那儿偷师来的。没播放时灰色,一播放就绿了,还显示"正在播放",唱片开始转,右边冒出节奏感很强的音频条。说实话右边的音频条有点多余,但去掉又太空,所以留着了。

我最喜欢的是向上飘的音符,不仅大小透明度变,还加了模糊和旋转。代码写得可复杂了:

const SpotifyPlayAnimation: React.FC<{ isPlaying: boolean }> = ({ isPlaying }) => {
  const IconVariants = useCallback((i: number) => ({
    initial: { opacity: 0, scale: 0, y: 0, x: 0, rotate: 0, rotateY: 0, filter: 'blur(0px)' },
    animate: {
      opacity: [0, 1, 0],
      scale: [0, 0.8, 1.2, 0.4],
      y: [-5, -50],
      x: [0, i % 2 === 0 ? 12 + i * 5 : -(12 + i * 5)],
      rotate: [0, i % 2 === 0 ? 5 + i * 2 : -(5 + i * 2)],
      rotateY: [0, 20, 45],
      filter: ['blur(0px)', 'blur(0px)', 'blur(2px)'],
      transition: {
        duration: 3,
        repeat: Infinity,
        ease: "easeInOut",
        times: [0, 0.4, 1],
        delay: i * 0.5,
      }
    },
  }), [])
 
  const musicIcons = useMemo(() => {
    return Array(5).fill(null).map((_, i) => ({
      type: Math.random() < 0.5 ? 'icon1' : 'icon2',
      position: i % 2 === 0 ? `left-${1 + i * 2}/8` : `right-${1 + i * 2}/8`
    }))
  }, [])

歌曲信息用 SWR 实时更新的,挺好用的。

唱片封面的圆形纹理和光泽用 CSS 做的,比图片快。

图片加载的模糊效果

图片加载的时候加了模糊渐变的效果,参考了 Loading Images With the "Blur Down" 这篇文章。比起直接显示加载圈或者空着,好看多了。

好物页面的布局动画

好物页面用了 Framer Motion 的 Layout 动画,点开图片的时候,它会从原来的位置优雅地飘起来。看着很舒服。

如果你想试试这种效果,看看 Layout animations 的文档。

那些容易被忽略的小动画

网站里还有很多不起眼的小动画。比如代码块右上角的复制按钮,点完复制不会直接变图标,有个过渡效果,看着舒服点。这些小细节加起来才让网站整体顺滑。

代码大概这样:

<Tooltip content="复制代码">
  <button
    onClick={copyToClipboard}
    className="group absolute right-2 top-[7px] rounded-xl transition-all duration-300"
  >
    <Icon
      name="clipboard"
      className={c(
        "size-5 transition-all duration-300",
        isCopied ? "scale-0 opacity-0" : "scale-100 opacity-100",
        "text-neutral-400 dark:text-neutral-500 group-hover:text-neutral-500"
      )}
    />
    <Icon
      name="tick"
      className={c(
        "absolute left-1/2 top-1/2 size-5 -translate-x-1/2 -translate-y-1/2 transition-all duration-300",
        isCopied ? "scale-100 opacity-100" : "scale-0 opacity-0",
        "text-green-500"
      )}
    />
  </button>
</Tooltip>

写在最后

网站里其实还有不少其他动画,这里就先聊这些。以后有时间会继续优化。

做这个网站的过程挺有意思的,不仅学到了 Next.js 和 Framer Motion,还在想怎么把想法变成真的。每个动画、每个交互,都是在琢磨怎么让体验更好点。

希望这些小细节能给你带来点惊喜。


上一篇自定义动画曲线的应用post,
下一篇Framer Motion Transition 属性详解post,