使用 CSS 和 JS 实现博客导航栏线条动画效果
原理
SVG 描边动画(stroke animation)是一种通过控制 SVG 元素的描边(stroke
)属性,让路径逐步显示出来,产生 “手写” 或 “线条绘制” 的视觉效果。它的核心是使用:
stroke-dasharray
:设置虚线的模式,等同于 “路径总长”,将整个路径打散成可动画的虚线段。stroke-dashoffset
:设置虚线的偏移距离,将整条线 “推出可见范围”,通过动画逐渐归零,就像笔迹被画出来一样。@keyframes
:通过 CSS 的@keyframes
或 JS 动态设置这两个属性,实现逐步描边。
这样就能模拟路径从无到有的 “被画出” 的过程。所以首先我们得准备好一个能用的 SVG 图(什么是 SVG?)。
方法一
你可以使用 Adobe 的 adobe illustrator 来徒手绘制 svg 图。
怎么操作呢,就是创建好画布之后直接使用铅笔 / 钢笔 / 画笔等工具进行一个手画。
例如使用铅笔工具:
左边能看到路径锚点,右边能看到各个图层。
紧接着将其导出为 svg:
选项 | 推荐设置 |
---|---|
样式 | 内部 CSS |
字体 | 转换为轮廓 |
图像 | 保留(无图也行) |
对象 ID | 保留(方便调试) |
小数位数 | 3 |
点击显示代码,它将调用文本编辑器让你提前查看。
svgd 代码大致是:
1
2
3
4
5
6
7
8
9
10
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232.798 65.792">
<defs><style>.a{fill:none;stroke:#000;stroke-miterlimit:10;}</style></defs>
<title>未标题-1</title><path class="a" d="M166.57,148.587c-7.072.465-13.436,4.25-19.491,7.933-3.19,1.941-6.427,3.92-8.947,6.676-2.646,2.9-4.364,6.5-6.044,10.041-2.088,4.406-4.207,9.467-2.374,13.985a16.6,16.6,0,0,0,3.792,5.134,131.478,131.478,0,0,0,11.071,10.113,10.374,10.374,0,0,0,4.293,2.435,10.787,10.787,0,0,0,4.654-.435l18.454-4.506a.6.6,0,0,0-.954-.537" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M185.9,171.121a4.907,4.907,0,0,1,3.356,3.268" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M184.46,189.411a72.961,72.961,0,0,1,2.635,20.9" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M224.639,195.225a13.226,13.226,0,0,0-2.332-4.606,4.5,4.5,0,0,0-4.659-1.511,6.49,6.49,0,0,0-1.927,1.276q-3.384,2.889-6.612,5.953c-1.78,1.689-3.616,4.431-2.06,6.329a5.055,5.055,0,0,0,3.076,1.384,15.353,15.353,0,0,0,5.2.412,5.2,5.2,0,0,0,4.119-2.853c.621-1.5.234-3.2.241-4.829a9.808,9.808,0,0,1,1.229-4.7c.318,2.958.662,6,2.08,8.618s4.193,4.726,7.154,4.442" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M264.978,149.682q-5.542,20.928-9.175,42.3a5.173,5.173,0,0,0,.306,3.722,4.7,4.7,0,0,0,2.181,1.548,25.766,25.766,0,0,0,8.293,2.02" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M301.091,144.6c-1.35,8.821-2.7,17.644-3.829,26.5-.958,7.52-1.743,15.268.208,22.594.763,2.864,2.659,6.114,5.619,5.968" transform="translate(-128.515 -144.528)"/>
<path class="a" d="M355.807,194.125c2.7-2.028,4.855-5.064,5-8.441.477-11.264-19.128-16.029-23.575-5.714-1.68,3.9-.743,11.83,1.3,15.42C342.345,202.118,350.785,197.892,355.807,194.125Z" transform="translate(-128.515 -144.528)"/>
</svg>
在导出的 svg 代码中,我们最关心的是:
- 有
stroke
且fill="none"
:动画只作用于描边部分,必须明确设置无填充 - 有使用到
<path>
元素:做描边动画的图形元素,支持路径长度计算 - 有
viewBox
:在不同尺寸下能比例正确
方法二
你也可以使用 adobe illustrator 的文字工具指定字体,来生成好看的字体效果,例如:
这个时候你只需要用选择工具,框选文字转换轮廓就可以得到复杂路径图层:
创建之后,按住 ctrl 键,你会发现文字上就出现了许多路径锚点:
图层中也自动有了每个字母的复合路径:
可以和方法一一样直接导出了。
1
2
3
4
5
6
7
8
9
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 203 74.13">
<title>未标题-1</title>
<path d="M259.914,163.879c1.1-.439,16.567-6.8,17-16.9,0-.109,1.537-6.144-12.835-6.693-26.661-.987-52,20.956-52.662,41.362,0,1.536-2.634,24.027,22.82,24.905S281.2,189.881,281.2,189.881s.987-.878,1.536.219c0,.11.22,2.085-3.511,5.266-1.206,1.1-21.722,15.909-43.556,15.031-16.456-.658-29.4-9-28.635-28.855.769-22.491,28.526-46.3,56.832-45.2,1.1,0,17.225.22,17.334,10.422.11,10.094-15.8,18.981-17.225,19.749-1.426.878-5.375,2.413-6.472.109C256.513,164.647,257.72,164.647,259.914,163.879Z" transform="translate(-207 -136.305)"/>
<path d="M294.358,202.717c-8.557.11-9.435-10.094-9.216-12.069.22-2.413,1.1-7.35,1.1-7.35.768-1.975,4.168-1.756,4.168.219,0,.549-4.168,16.018,4.389,15.579,5.815-.22,11.3-10.093,13.824-14.7.987-1.865,2.523.658.329,5.046C308.182,190.978,302.147,202.717,294.358,202.717Zm-3.291-27.537a2.882,2.882,0,0,1-3.292,2.413,2.787,2.787,0,0,1-2.413-3.182c0-.987,1.1-3.73,3.4-3.4C291.177,171.339,291.286,173.643,291.067,175.18Z" transform="translate(-207 -136.305)"/>
<path d="M327.82,202.278c-6.584.109-7.571-6.582-7.571-6.582s-3.95,7.24-7.79,7.131c-2.084,0-7.35-1.865-6.033-12.179s8.228-14.921,11.739-14.81c6.363.218,5.815,5.7,5.7,6.253-.877,2.853-1.974,2.084-2.742,1.755,0,0,.658-4.608-3.072-4.717-3.181-.11-6.363,4.717-7.46,9.654-1.207,5.047-.439,10.2,2.193,10.2,4.06,0,7.461-10.972,7.9-13.714.218-1.1,4.278-.22,4.058,1.535-.109,0-4.058,11.739,3.292,11.739,3.292,0,7.57-3.4,13.383-14.043.989-1.755,2.526.769.221,5.048C339.779,193.063,334.292,202.169,327.82,202.278Z" transform="translate(-207 -136.305)"/>
<path d="M361.938,184.615c1.1-1.756,2.523.767.218,5.046-.768,1.536-6.582,13.6-17.223,13.056-12.947-.659-13.275-15.031-13.166-27.1.108-10.642,6.472-32.365,14.92-31.817,5.376.329,7.352,6.912,5.7,17.664-2.084,14.591-10.642,28.964-14.153,33.024,1.976,3.071,4.718,4.936,7.9,4.936C354.148,199.425,359.414,189.223,361.938,184.615Zm-15.36-36.754c-4.17-.329-10.093,13.165-11.519,27.977-.331,4.5.327,12.068,1.754,16.017,5.7-8.228,7.462-12.507,11.411-28.086C348.992,160.917,350.747,148.08,346.578,147.861Z" transform="translate(-207 -136.305)"/>
<path d="M382.561,184.615c1.1-1.756,2.524.767.219,5.046-.768,1.536-6.582,13.6-17.223,13.056-12.948-.659-13.275-15.031-13.166-27.1.108-10.642,6.472-32.365,14.92-31.817,5.376.329,7.352,6.912,5.705,17.664-2.084,14.591-10.643,28.964-14.153,33.024,1.976,3.071,4.718,4.936,7.9,4.936C374.771,199.425,380.038,189.223,382.561,184.615ZM367.2,147.861c-4.171-.329-10.093,13.165-11.519,27.977-.331,4.5.327,12.068,1.753,16.017,5.706-8.228,7.463-12.507,11.411-28.086C369.616,160.917,371.371,148.08,367.2,147.861Z" transform="translate(-207 -136.305)"/>
<path d="M408.451,184.615c.769-1.427,2.085,0,1.316,2.742-1.206,3.621-5.7,8.557-13.933,6.693-1.536,4.937-4.826,9.435-10.311,9.106-5.268-.329-6.7-6.473-6.364-10.423s2.522-12.4,6.143-12.946a5.006,5.006,0,0,1,4.937-4.169c2.853-.22,7.459,3.291,6.693,12.836a12.294,12.294,0,0,1-.219,1.975c3.619,1.426,8.117.988,10.641-3.95Zm-21.943,15.359c2.744.219,5.158-3.62,6.145-7.022a14.8,14.8,0,0,1-6.583-7.789c-1.207.878-3.073,5.7-3.181,7.9C382.778,195.476,382.889,199.755,386.508,199.974Zm4.06-20.626c-1.865.11-2.084,2.743-1.426,4.609a9.361,9.361,0,0,0,4.169,4.936C393.421,184.944,393.2,179.129,390.568,179.348Z" transform="translate(-207 -136.305)"/>
</svg>
仔细观察会发现这种方式得到的 svg 代码是不符合我们的要求(见方法一)的。还需要全选到所有的路径,在属性中将填充设为 none 才行。
方法三
借助在线网站:Google Font to Svg Path 完成 Svg 的生成。
该网站提供谷歌字体供选择,也支持自己上传字体,使用起来是很方便的。
其中:
- variant:表示你选择的字体的风格 —— 正常还是斜体
- text:输入你要生成 svg 的语句或单词
- union:这个影响 svg 路径,是否要将字母看作整体
- kerning:字距
- separate characters:字符是否分离
- bezier accuracy:跟字体像素有关
- Dxf Units:尺寸单位
- Fill:填充颜色
- Stroke:线条颜色
- Stroke Width:线条宽度
- Non-scaling stroke:是否可缩放
- Fill rule:填充的规则
参数的变动会实时的更新在下方的 svg 元数据框中。
这种方法生成的 svg 也是不符合我们的要求(见 fang'fa)的,还需要到能处理 svg 的软件中将它的填充设为 none
编写 js
有了上述方法,可以得到 svg 图。这里简单说说我的方案:
- 字体:Signerica_Medium
- Google Font to Svg Path:仅 Union 勾选,Size 为 100。其余默认
- 待生成文字为:CiaoHello
当然,还需要到 Adobe Illustrator 中将填充设置成无。
有了 svg 之后,在 hexo 博客的 source\js
目录下创建 sign.js
1
2
3
4
5
6
7
8
9
10
11
const navbarBrand = document.querySelector('.container a');
// 替换navbarBrand中的strong标签为svg标签
navbarBrand.innerHTML = `
<svg class="svg" viewBox="0 0 682.2 98.507" xmlns="http://www.w3.org/2000/svg">
...略...
</svg>
`;
const paths = document.querySelector('.container .navbar-brand .svg path')
// console.log(paths.getTotalLength());
const len = paths.getTotalLength()
paths.style.setProperty('--l', len)
为什么这么写?
这里主要是利用浏览器开发者工具查看你的网站元素结构,找到导航栏的标题位置:
可以看到类名为 navbar-brand
的就是导航栏的标题区域,我们就是要将 svg 放在这里。
上图我已经做出修改,如果没有进行修改的话,你这里的 <svg>
标签应该是依照你的_config.fluid.yml
中设置的导航栏标题也就是 <strong>
标签。
修改的语句也很简单,先找到父亲 navbarBrand
1
const navbarBrand = document.querySelector('.container a');
接着使用.innerHTML
设置为 svg 即可:
1
2
3
4
5
navbarBrand.innerHTML = `
<svg class="svg" viewBox="0 0 682.2 98.507" xmlns="http://www.w3.org/2000/svg">
...略...
</svg>
`;
这样只是先将 svg 放在这里了,还需要明白怎么写动画。
可以参考文章:https://juejin.cn/post/6955857586922979364
或者视频:https://www.bilibili.com/video/BV1Qi4y1Y7Sp/
真不是摆烂,大佬讲的是真香
所以现在还需要知道每条 path 的长度,可以使用当前选择的元素.getTotalLength()
得到:
1
2
3
const paths = document.querySelector('.container .navbar-brand .svg path') // 进一步选择到path
const len = paths.getTotalLength()
paths.style.setProperty('--l', len)
如果你在之前生成 svg 的时候,生成了多条 path,这里你应该使用的是:
1
2
3
4
5
const paths = document.querySelectorAll('.container .navbar-brand .svg path')
paths.forEach(path => {
const len = path.getTotalLength()
path.style.setProperty('--l', len)
})
给每条 path 设置上行内样式为变量 --l
方便后面 css 的编写。
编写 CSS
在 source\css
目录下创建 sign.css
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.svg {
width: 200pt;
height: 30pt;
}
.svg path {
stroke: white;
stroke-width: 3pt;
stroke-linecap: round;
stroke-dasharray: var(--l);
stroke-dashoffset: var(--l);
fill: none;
fill-rule: nonzero;
animation: stroke 25s forwards;
-webkit-animation: stroke 25s forwards;
}
@keyframes stroke {
to {
stroke-dashoffset: 0;
/* fill: white; */
}
}
通过样式变量使得 stroke-dasharray
和 stroke-dashoffset
属性能轻易获取到 path 的长度,也就方便了 @keyframes stroke
的编写。
另一个方法
其实有个库可以实现这种效果:Vivus.js
demo:https://maxwellito.github.io/vivus/
GIthub 地址:https://github.com/maxwellito/vivus
使用方法也很简单,只要给 svg 标签加上 id 即可:
1
2
3
4
5
6
7
8
9
<svg id="my-svg">
<path...>
<path...>
<path...>
</svg>
<script>
new Vivus('my-svg', {duration: 200}, myCallback);
</script>