news 2026/10/1 8:29:00

用 SVG 内联动画打造灵动波浪下划线:30-seconds-of-code 的 Squiggle Link Hover Effect 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 SVG 内联动画打造灵动波浪下划线:30-seconds-of-code 的 Squiggle Link Hover Effect 详解
  • 教程
  • 文档

【免费下载链接】30-seconds-of-code

Coding articles to level up your development skills

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-code
点击查看免费下载

链接的 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; }

这里做了三件事:

  1. text-decoration: none移除浏览器默认的原生下划线;
  2. color: inherit让链接文字继承周围文本颜色,不额外强调;
  3. 用一个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-width2波浪线粗细,调大需同步考虑viewBox高度与background-size
viewBox0 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

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-code
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 8:28:03

「台阶高度」差一点,整机的质感就没了|电子烟外壳台阶高度检测

问一个简单的问题&#xff1a;你们产线上的「台阶高度」&#xff0c;现在是怎么判合格的&#xff1f;如果答案是「老师傅看」「拿塞尺卡一下」「抽检几个」&#xff0c;那大概率不是标准不够严&#xff0c;而是手上的工具就给不出更细的数据。消费电子的品牌溢价&#xff0c;很…

作者头像 李华
网站建设 2026/10/1 8:27:59

基于OpenCV的笔迹识别系统:预处理、特征提取与比对实战

简介&#xff1a;这份资源是面向高校计算机、软件工程等专业学生的毕业设计完整资料包&#xff0c;围绕OpenCV图像识别技术实现笔迹识别系统&#xff0c;适合正在准备相关课题或需要图像处理实战案例的学习者。压缩包共29个文件&#xff0c;约43.92MB&#xff0c;包含2个Python…

作者头像 李华
网站建设 2026/10/1 8:26:41

DeepSeek总结的使用维度表加速DuckDB字符串聚合

使用维度表加速字符串聚合 DuckDB 团队 2026-10-02 | 16 分钟 摘要&#xff1a;当查询按冗长、重复的字符串进行分组时&#xff0c;将这些字符串移入一个带有已排序、窄整数键的小型维度表中。在键上进行聚合&#xff0c;最后再将字符串连接回来。查询的工作方式与之前相同&…

作者头像 李华
网站建设 2026/10/1 8:26:32

《红楼梦》的处世哲学「世事洞明皆学问,人情练达即文章。」一部写尽繁华与幻灭的书,最终教给世人的,是如何在无常里把人做稳、把心安放。壹先立总纲:它到底在讲什么《红楼梦》常被当成爱情小说、家族小

《红楼梦》的处世哲学「世事洞明皆学问&#xff0c;人情练达即文章。」 一部写尽繁华与幻灭的书&#xff0c;最终教给世人的&#xff0c;是如何在无常里把人做稳、把心安放。壹先立总纲&#xff1a;它到底在讲什么《红楼梦》常被当成爱情小说、家族小说、政治寓言&#xff0c;但…

作者头像 李华