- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
链接的 hover 效果是网页交互中最细小却最能体现设计巧思的细节之一。本文将以 30-seconds-of-code 仓库中的 squiggle-link-hover-effect.md 为主体,完整拆解一种极具"顽皮感"的波浪下划线(squiggle underline)实现方案:把一段自带 CSS 动画的 SVG 路径经 data URI 编码后作为链接的background-image,在 hover 时替换为一条永远流动的波浪线。读完本文,你将掌握 SVG 内联编码、background-image多层切换、以及利用linear-gradient做"静态下划线 → 动态波浪线"平滑过渡的完整实战技巧,并能在此基础上自由定制颜色、粗细、速度与流动方向。
效果原理:为什么用 SVG 背景图
这个技巧的关键在于把SVG 图片用作链接的background-image。与直接用伪元素画一条线不同,SVG 的优势在于:
- 波浪路径本身就在 SVG 内部定义,几何形状完全可控;
- SVG 内部可以携带
<style>块和@keyframes,动画逻辑与页面 CSS 解耦,背景图自己就会"动"; - 配合
background-repeat: repeat-x,单段路径会横向无缝平铺,形成一条横贯整个链接宽度的波浪线。
动画本身非常简单:对路径做一次沿 x 轴的平移,让每个平铺单元里的波浪持续左移,视觉上便产生了"波浪在流动"的错觉。整个过程只需要 CSS,零 JavaScript。
原始 SVG:一条会"流动"的波浪路径
先看未编码的 SVG 源文本,它定义在一个20 x 4的坐标系中:
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'> <style type='text/css'> .squiggle { animation: shift .3s linear infinite; } @keyframes shift { from { transform: translateX(0); } to { transform: translateX(-15px); } } </style> <path fill='none' stroke='#0087ca' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3' /> </svg>拆解几个关键设计:
viewBox='0 0 20 4':坐标系宽 20 个单位、高 4 个单位。4px 的高度直接决定了后续 CSS 中background-size: auto 4px的取值,也保证波浪线恰好贴合文字底部。animation: shift .3s linear infinite:每 0.3 秒循环一次,linear缓动保证匀速、无顿挫,infinite让流动永不停止。@keyframes shift:路径从translateX(0)移动到translateX(-15px)。注意这里只平移 15px 而不是 20px——路径的水平周期是 20px,位移 15px 会让相邻两个平铺单元的相位错开,形成连续流动的错觉;如果改成恰好 20px,每个单元会"无缝复制"而看不出流动。d路径数据:以M0,3.5起笔,用贝塞尔曲线命令c(三次贝塞尔)与s(平滑延伸)交替勾勒出两段波峰波谷。c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3表示在 20px 的宽度内画出两个对称起伏,恰好构成一个可重复的波浪周期。stroke='#0087ca'与stroke-width='2':蓝色描边、粗细 2px,fill='none'表示只画线不填充,避免出现实心块。
将 SVG 编码进 CSS:data URI 的完整转换
由于 CSS 的url()不接受换行与未转义的特殊字符,必须把上面的 SVG 压缩成一行并做URL 编码,得到data:image/svg+xml;charset=utf8,...的形式:
a.squiggle:hover { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-15px);}}%3C/style%3E%3Cpath fill='none' stroke='%230087ca' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E"); background-size: auto 4px; }编码规则很直白,可以与上面的源文本一一对应:
<→%3C,>→%3E;- 十六进制颜色里的
#→%23(如stroke='%230087ca'),否则会被当作 URL 的锚点分隔符解析; - 内部所有属性仍用单引号包裹属性值,避免与 CSS 外层
url("...")的双引号冲突; charset=utf8声明编码,压缩掉换行与多余空白以减小体积。
这份编码后的版本就是原文档中 hover 状态使用的完整 background 数据,直接复制即可运行。
组装到链接上:HTML 与静态下划线
接下来需要一段 HTML 让效果落地:
<p>The <a class="squiggle" href="#">magnificent octopus</a> swam along gracefully.</p>对应的基础样式如下:
a.squiggle { background-image: linear-gradient(to bottom, #0087ca 0%, #0087ca 100%); background-position: 0 100%; background-repeat: repeat-x; background-size: 2px 2px; color: inherit; text-decoration: none; }这里做了三件事:
text-decoration: none移除浏览器默认的原生下划线;color: inherit让链接文字继承周围文本颜色,不额外强调;- 用一个
linear-gradient(to bottom, #0087ca 0%, #0087ca 100%)作为背景图——它本身是一段"从蓝到蓝"的纯色渐变,配合background-repeat: repeat-x与background-size: 2px 2px,就变成了一条2px 高的静态蓝色下划线。
而background-position: 0 100%的作用是把这条下划线定位到链接内容的底部,与普通文字下划线位置一致。这一整套"渐变假下划线"的写法,正是为 hover 时无缝切换到 SVG 波浪线做的铺垫。
hover 状态:SVG 替换渐变,波浪开始流动
当鼠标悬停时,规则切换为:
a.squiggle:hover { background-image: url("data:image/svg+xml;charset=utf8,..."); /* 完整编码见上文 */ background-size: auto 4px; }关键点在于:
background-image由linear-gradient整体替换为 data URI 编码的 SVG,两者不可能同时显示,因此过渡是瞬间且干净的;background-size: auto 4px让背景保持 SVG 原始坐标系的高度(4px),宽度按比例自动缩放;background-repeat: repeat-x、background-position等属性继承自基础规则,不需要在 hover 中重复声明,SVG 单元因此沿水平方向无缝平铺;- 每个平铺的 SVG 单元内部,
.squiggle路径持续执行translateX(-15px)的位移动画,于是整条下划线看起来就像水流一样持续向右/向左滚动。
整个过程只有一张背景图的属性级替换,没有任何 DOM 操作、伪元素或 JavaScript,性能开销极小,非常适合作为全站链接的统一交互细节。
参数速查与自定义方向
把核心参数整理成速查表,方便按需调优:
| 参数 | 示例值 | 作用与调整建议 |
|---|---|---|
stroke | #0087ca | 波浪线颜色,与静态下划线linear-gradient中的颜色保持一致,否则 hover 切换时会有色差 |
stroke-width | 2 | 波浪线粗细,调大需同步考虑viewBox高度与background-size |
viewBox | 0 0 20 4 | 坐标系尺寸,高度 4 直接对应background-size: auto 4px |
animation: shift .3s linear infinite | .3s | 流动速度,数值越小越快;linear保证匀速 |
translateX(-15px) | -15px | 单次位移量,与 20px 周期配合产生相位流动感 |
background-size: 2px 2px(静态) | — | 非 hover 时下划线的高度与重复单元尺寸 |
background-size: auto 4px(hover) | — | hover 时 SVG 的显示尺寸,宽度自适应 |
反向流动:把@keyframes shift中的位移改为translateX(15px)(正值),波浪便会朝相反方向流动。
更大更粗的波浪:同时提高viewBox高度(如0 0 20 6)、stroke-width与 hover 的background-size(如auto 6px),并相应增加translateX的位移量,即可得到更夸张的波浪形态。修改后记得同时更新静态下划线的background-size高度,让两种状态仍然衔接自然。
仓库中的位置与姊妹效果
该技巧位于仓库的 content/snippets/css/s/squiggle-link-hover-effect.md,归属于 CSS 的 Hover Effects 集合(见 content/collections/css/hover-effects.yaml),并带有animation、visual两个标签,同属于 content/collections/css/visual.yaml 描述的视觉类文章体系。
同集合中有多个风格互补的下划线/悬停方案可供对比学习:
- hover-underline-animation.md:使用
::after伪元素配合transform: scaleX(0/1)与transform-origin,实现下划线从左侧"生长"到右侧的单次动画——它是一次性的,而波浪线是无限流动的; - button-hover-transitions.md:涵盖放大、缩小、填充、摆动、边框等五种按钮过渡,适合批量理解
transition与@keyframes的用法; - mouse-cursor-gradient-tracking.md:用 CSS 变量加少量 JavaScript 实现渐变跟随鼠标,展示了 hover 交互的另一种进阶形态。
需要说明的是,data URI 内嵌 SVG 的写法依赖现代浏览器对 SVG 背景图与内部 CSS 动画的支持;此外链接是行内元素,若希望波浪线严格按照内容盒尺寸铺满,可考虑配合display: inline-block使用。克隆仓库后,在content/snippets/css/s/目录下可以浏览全部 78 篇 CSS 文章,将这些 hover 技巧组合使用,足以构建一套完整且富有细节的站点交互体系。
- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
相关推荐
30 Seconds of Code:CSS 悬停下划线生长动画(Hover Underline Animation)从原理到实战
30 Seconds of Code:CSS 悬停下划线生长动画(Hover Underline Animation)从原理到实战 本指南以 30 Second
教程文档30-seconds-of-code 实战:五种 CSS Button Hover Transitions 动画实现指南
30 seconds of code 实战:五种 CSS Button Hover Transitions 动画实现指南 CSS 过渡(transition)是
教程文档30 Seconds of Code:用 CSS 缓动变量(Easing Variables)打造高级动画节奏
30 Seconds of Code:用 CSS 缓动变量(Easing Variables)打造高级动画节奏 导读 本文源自 30 seconds of co
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考