聊聊CSS中文本下划线

发布于 大漠

在Web中给文本添加下划线常常出现在<a>链接的文本上,早期一般使用text-decoration属性给文本添加下划线、删除线等。除了text-decoration之外,CSS还有很多技术方案可以给文本添加下划线效果,比如border-bottombox-shadowbackground-image等。对于Web开发者而言,更庆幸的是,CSS还有更多的,更灵活的特性实现文本下划线的效果。在这篇文章中,将和大家一起聊聊CSS中其他的特性怎么实现一个更有创意的效果。

新的text-decoration特性

text-decoration并不是一个新特性,在CSS 2.1中,text-decoration就可以使用noneunderlineoverlineline0-through给文本添加下划线、删除线等效果。只不过,在新的CSS规范中(CSS Text Decoration Module Level 3Level 4)添加了一些新特性。比如:

text-decoration-line: none | [ underline || overline || line-through || blink ]
text-decoration-style: solid | double | dotted | dashed | wavy
text-decoration-color: <color>
text-decoration-thickness: auto | from-font | <length>
text-decoration-skip: none | [ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || edges || box-decoration ]
text-decoration-skip-ink: auto| none

其中text-decoration-linetext-decoration-styletext-decoration-color还可以简写成text-decoration

text-decoration: <text-decoration-line> || <text-decoration-style> || <text-decoration-color>

除些之外,还新增了text-underline-positiontext-underline-offset属性给文本设置下划线样式:

text-underline-position: auto | [ under || [ left | right ] ]
text-underline-offset: auto | <length>

来看一个简单的示例:

自定义下划线效果

文章开头就提到过,除了使用text-decoration-*text-underline-*属性可以给文本添加下划线效果之外,还可以使用一些其他方法来给文本添加自定义下划线的效果,比如下面两篇文章中提到的方法:

随着CSS的ClippingMasking技术越来越成熟,我们可以配合CSS的伪元素实现一些更有创意的下划线效果。

使用clip-path给文本添加下划线

@Travis Almand有篇文章专门介绍clip-path实现的不同动画效果。比如下面这样的一个效果:

div {
    width: 200px;
    height: 200px;
    background-color: #f36;
    animation: melt-enter 2s ease-in alternate infinite,melt-leave 4s ease-out 2s alternate infinite;
    cursor: pointer
}

@keyframes melt-enter {
    0% {
        clip-path: path('M0 -0.12C8.33 -8.46 16.67 -12.62 25 -12.62C37.5 -12.62 35.91 0.15 50 -0.12C64.09 -0.4 62.5 -34.5 75 -34.5C87.5 -34.5 87.17 -4.45 100 -0.12C112.83 4.2 112.71 -17.95 125 -18.28C137.29 -18.62 137.76 1.54 150.48 -0.12C163.19 -1.79 162.16 -25.12 174.54 -25.12C182.79 -25.12 191.28 -16.79 200 -0.12L200 -34.37L0 -34.37L0 -0.12Z');
    }
    100% {
        clip-path: path('M0 199.88C8.33 270.71 16.67 306.13 25 306.13C37.5 306.13 35.91 231.4 50 231.13C64.09 230.85 62.5 284.25 75 284.25C87.5 284.25 87.17 208.05 100 212.38C112.83 216.7 112.71 300.8 125 300.47C137.29 300.13 137.76 239.04 150.48 237.38C163.19 235.71 162.16 293.63 174.54 293.63C182.79 293.63 191.28 262.38 200 199.88L200 0.13L0 0.13L0 199.88Z');
    }
}

@keyframes melt-leave {
    0% {
        clip-path: path('M0 0C8.33 -8.33 16.67 -12.5 25 -12.5C37.5 -12.5 36.57 -0.27 50 0C63.43 0.27 62.5 -34.37 75 -34.37C87.5 -34.37 87.5 -4.01 100 0C112.5 4.01 112.38 -18.34 125 -18.34C137.62 -18.34 138.09 1.66 150.48 0C162.86 -1.66 162.16 -25 174.54 -25C182.79 -25 191.28 -16.67 200 0L200 200L0 200L0 0Z');
    }
    100% {
        clip-path: path('M0 200C8.33 270.83 16.67 306.25 25 306.25C37.5 306.25 36.57 230.98 50 231.25C63.43 231.52 62.5 284.38 75 284.38C87.5 284.38 87.5 208.49 100 212.5C112.5 216.51 112.38 300.41 125 300.41C137.62 300.41 138.09 239.16 150.48 237.5C162.86 235.84 162.16 293.75 174.54 293.75C182.79 293.75 191.28 262.5 200 200L200 200L0 200L0 200Z');
    }
}

效果如下:

如果你的浏览器没看到任何效果的话,请更换Firefox 63+浏览器查阅,你将会看到下面这样的效果:

将这个创意放到文本下划线中也是可以的,只不过需要借助CSS的伪元素:

div {
    display: inline-flex;
    font-size: 30px;
    position: relative;
    cursor: pointer;
    
    &::after {
        content: '';
        position: absolute;
        top: calc(100% + 6px);
        left: 0;
        right: 0;
        height: 10px;
        background-color: currentColor; 
        animation: 2s melt-enter;
    }
    
    &:hover::after {
        color: #f36;
        animation: 2s melt-leave;
    }
}

效果如下:

使用SVG实现自定义下划线效果

使用clip-path给文本添加下划线效果,可以帮助我们实现很多具有创意的效果,还可以配合CSS的animation来实现带有动画效果的下划线。其实,除了上面提到的方案之外,我们还可以在background-image中使用SVG给文本添加很多与众不同的下划线效果。比如:

上面的示例中引用像下面这样的一个使用SVG绘制的线条:

<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 260 15.6">
    <style>
        .st0{fill:#f3bc34}
    </style>
    <path class="st0" d="M206.8 7.3l-.1.3c.1-.2.2-.3.1-.3zM234.7 10h-.1c-.2.4-.1.3.1 0zM54.8 4.2l-.6-.4c.2.4.4.5.6.4zM17.1 5.1zM34.5 9.6l.1.3c0-.2 0-.3-.1-.3zM22.4 10.8c-.3-.1-.7-.1-1-.1.2.1.7.1 1 .1zM17.5 5c-.1.1-.2.1-.4.2.2-.1.3-.2.4-.2zM52.7 9.8l.5.9c-.1-.3-.3-.6-.5-.9zM19.5 11.6c-.2-.2-.4-.2-.6-.3 0 .2.3.3.6.3zM120.9 11.4c-.1.1-.2.2-.2.3.3-.1.3-.2.2-.3zM80.9 10.4h-.1s.1.1.2.1l-.1-.1zM92.6 10.4l-.2.2c.2-.1.2-.1.2-.2zM72.1 11.3c-.1.1-.3.2-.4.3l.4-.3z"/>
    <path class="st0" d="M260 6c-1-.6-4.7-1.2-5.8.3-.2-.1.1-.3.2-.4-.9.2-2.2.1-3.6 0s-2.9-.2-4.2 0c-1 1.5-3.9-.6-4.8 1.4l.5-.4c.9.5-1.2 1.4-1.5 1.9-.8-1.2-.1-1-1-2l1.1.4-.3-1c-3.1 2.8-6.2-.9-8.2 1.1.1-.1.1-.3.2-.4-1.4-.5-2.3.8-3.3 1.2-.1-.5.6-.9 1.1-1.3-2.4-.3-6.4 1.2-9 .4-.9.7.4.9-.6 1.5-.8-.2-1.4-.7-.4-1.1-2.3-1.2-7.6 1-11.1-.2-1.8.8-.7 1.1-3.5 1.6.7-.5-.7-1.7 1-1.7l.2-.5c-2.8-.1-6.6-.3-8.1 1.2-.1-1.1-.5-.2-1.6-.8-.4.1 0 .2.2.2-1 .9-1.6-.1-2.3.1l.3-.2-2 .7c-.3-.2-.8-.4-.9-.7v.8c-1.1 0-.5-1-1.9-.8l.3.6c-.9-.4-2.2.4-2.4-.5 0-.2.1-.1.4-.1-1.3-1.2-3.5.3-5.1-.3l.4 1.3c-1.6.4-1-.3-.9-.7-1.1 0-1.3-.4-2.7-.6-.7.3-.4.5-.6.8l-1.5-.4 1-.7c-2.3 1.8-5.6-.4-7.2 1.2-.8-.4.8-.7.3-1-2.6-.9-6 1-8.2 0-3.6-1-7.8-.4-11.8-1.1l.1.3-2.9-.4c-.8.7-2.7.3-4 1.1.1-.3-.1-.7.2-.9-1.2.1-2.6.4-3.3-.1l.4-.3c-2.7-.3-6.4-.5-7.9.1-.9 0-.9-.6-1.1-1-1.6-.1-2.6.2-3.9.7-.3-.2-.7-.3-1-.6l-.6.8c-.6-.1-.1-.7-.6-.9-2.5.9-5.3-.1-7-.1l.2.4c-.7.3-2.1-.3-1.2-.7-3.4-.6-5.1 1.2-9.6.8-.6-1.5-4.1.3-4.8-1.4-1.9.4-3.2-.3-4.5.6 0-.2-.2-.2.1-.3-.8-.6-3.3-.2-5.3.2l-.1-.5c-.9 1.2-4.2.9-4.9 2-.2-.2.4-.5.7-.7-1-1.1-1.8.5-3.1.2.1-.3-.3-.6 0-.8-4.4-1.2-10.6.7-16.3-.1-1.6 0 .6 1.2-1.5 1.1-.6-.6 1-1.1-.3-1.4-.9.7-1.3.5-2.6.5.2-.4 0-.6.9-.9-.7-.5-3.1.9-4.5 0 .1.3-.2.5-.5.7-2.1 1-4.9-.9-5.1-.4 0 0-.7.2-.1.3-.8 0-1.9-.2-1.7-.7-.4.3-.8.8-1.4.8l.3-.6c-.4.1-.8.5-1.1.6l.6.4c-.9-.5-2.6.8-2.6-.4h.3l-1.7-.5c-.7.5-1.3 1-2.5.9-.5-1.3-2.9-.2-4.3-.3l.1-.4c-1.1.6-4 .4-3.5.6-1.1 0-2.6-.2-2-.6-.8.1-2.7.1-3.2.9l-1.8-1c-1 1.6-3.6-.5-3.6 1.2-1-.2-.8-.6-1.5-.9-1.4.9-2.8.8-4.2.7v-.2c-1.4-.1-3.1.8-5.1 1l-.5-1.2c-1 .2-1.3 1.2-2.3 1-.2-.2 0-.3.2-.3-1 .3-2.3.1-3.1-.2-1.5 1-2.7.7-3.9 1.8-1.3-1 1.7-.6.6-1.6-2.2-.4-4.4.4-6.7 1.1-.2-.2 0-.4.1-.7 0 0-1.2.9-2.2 1.8C.9 8.3 0 9.4.5 10c-.5.9-1.2 1.4.9 2 .6-.5 2.5-1.3 2.9-.4l.1-.9c2.6-.6.4 1.8 3.6 1.6l-.7-.3c.6-.1 1.1-.7 1.8-.5.2.2-.2.4-.5.6.9-.5 1.7-.9 2.6-1.4.1.5.1.8-.4 1.2 2.5-.2-.6-1.6 2.4-1.4.6.4-.2.6-.5.9 1.4.7 2.3-.1 3.8-.6.1.8-.9.7.3 1.2-.3-.4-.5-1.1.5-1.2-.4.8.7.4 1.6.5-.2-.3-.1-.6.2-.8.4-.1.8.1 1.4.1l-1.1-.7c1.5-.8 2.4.3 3.6.6-.1.1-.3.3-.5.3 1.2.3 2.5.9 4.1.1l-.3.1c2.9-.9-1-1.3 2.4-2.2 1.1.1-.4 2.6 2.1 1.6-1.3-.6 1.6-1.7 3.1-2L32.4 10c.6 0 1.6-.5 2-.3l-.1-.3c-.2-1.3 1.9.1 3-.7-1.3 1.8-1.4 1.5-1.6 3.2 1-1 2.2-1.9 4.1-1.8l-1.5 1.4c2.5.2 5.5-1.9 7.6-3-.5 1 .3 1.4-.6 2.2l2.4-.3-.7 1.1c1-1.2 2.1-.4 1.9-1.9-.3.2-.2.4-.7.3.1-.4.5-1.4 1.7-1.3.9.3-.5.6-.2 1 .8-.6.9.3 1.7-.1l-.8-.6c.6-.9 1.4-.1 2.2-.5-1 .4-.7.9-.3 1.4l-.1-.1c.8-.1 1.6-.7 2-.2l-.5 1.2.9-.9c.3.1.6.6 0 .8 2.8.7-.1-2.5 3.6-1.5 0 .5-.4.8-1.4.5-.2.7.1 1.1 1.1 1.4v.1c1.9 0 4.4 0 5.6-.8.4.3 0 .6-.4.9 2.1.4 2.8-.7 5 .1l-1-.4c1.4-.6 4-.8 5.3.1l-.4.3c1.3-.7 3.5.6 4 0-.6-.4 0-.6-.8-1l3.4-.7.2 1.2 1.8-.4c-.4-.5 2.4.4 2.5-.7 1 .4-.4.9-.8 1.4 1-.3 1.1.2 2.1-.5l1 1.1 2.6-.7c-.1.1 0 .2-.1.3 1.2-.9 3.1.6 4.6-.9-.1.1-.1.1-.1.2.9-.8 2.9-.2 3.7 0 1.4-.2.6-1 .6-1.4 3.9.4 2.7.3 6-.9 2 1.4-2.4 2.1.1 3 .4-.6 2.1-1.1 4.1-1.3 1.8.5 4.8.9 6.5 1.9l-.2-.9 2.6-.4-1.5 1.2c.4-.3 1.7-.8 2.6-1.2 2.7-.7 1.4 1.9 3.5.7.1.1.1.2.2.3.7-.6 2.4-.3 4.4-.5l-.7 1.1-1.3-.3c.7 1.1 2.1-.1 3.4 0 1.3-.3.7-1.3 1.4-1.6.5.1 1.2-.2 1.6.1 1.1.4.1 1.3-.3 1.8 1-1.1 1.4-.9 3.6-1.3.1.5-.1.8-.4.9.5-.1.9-.3 1.2-.8l.7.7c2.5 1 2.6-2 5.6-1.5-.8.6 2.6 0 3.5.7-1.1.1.4 1.6-.2 2.3 2.4.5 1-1.3 3-1.4l-.9 1.3c1.9-.5.5-.7 2.4-1.1-.5.4.8.4-.3.8 2.5.2 1.9.1 4.1.3l.2-1.3c.7-.1 1 .5 1.2.7-.3 0-.8-.1-.7.1.8 1.2 1.4-.6 2.4.5-.2-.4-.5-1 .4-1.1-.3.8 1.4.8 1.4 1.2-.6-.6 2-.2 2.1-1 1 .7-.4.6-.4 1.1.9-1 3.7 0 4.6-.6 0 .1.1.1.1.2 1.2-.6 3-.7 5.3-1.5l-.8.7c2.2.4 1.4-1.5 3.3-1.5-.4 1.1 3.1 0 2.2 1.2 1.1-.6 2.3-.8 3.1-1.7 1 .6-2.1 1.4-.6 1.8l1.6-.5.3.6c.1-.4 1.5-.4 1.4-.8.2.7.9 1.2.8 1.8 1-.2 2.4.5 3.3-.1l.1.3c1-1.3 3.1-.2 3.6-1.5l.6.7c1.5-.1 1.3-1.5 2-1.8.6 0 1.4-.2 2 0-2 .8 1 1.1 1.4 1.6.8 0 3.1 0 3.7-.7-1 .7-.4 1.2-2.1 1.3.9 1.3 2.6-.2 4.5-.1v.6c2.7-.4 2.8-1.5 4-2.5.3.8.1 1-.7 1.7 1.8.5 4.7-.1 6.7 0 .6.5.2.9-.5 1.1 2.1-.6 4.7.1 6-1.2-.5.5.9.3 1.6.6 0-.3.1-.6.2-.6 1.3-.6 4.1-1.1 5.6-.7l-.5.4c1.7.1 3-.5 4.3-.9 1.3-.4 2.6-.8 4.5-.4.7.3-.7 1.1.7 1 .7-.5.4-1.5 2.2-1.3l-.1.9 1.2-.9c-.7-.7-2.6-.4-1.3-1.2 1.6.8 1.3-.9 3.3 0-.4.1-1 .8-1.3 1.2 2 .4 3.4.1 4.8-.1 1.4-.3 2.8-.6 4.9-.2 2-.8 4.6-1.2 5.9-1.9 0 .9 0 1.7-.8 2.4 1.8 0 2.4-2.1 3.7-.9.7-1.3 4.7-1.2 5-3l2-.8z"/>
    <path class="st0" d="M58.1 11.1c-1 0-1.9 0-2.3.2.2.2 2.3.6 2.3-.2zM208.2 13.3c-.1 0-.3.1-.4.1.1 0 .3 0 .4-.1zM216.3 12.9c-.1-.1-.2-.2-.4-.3 0 .3.1.5.4.3zM132.6 11.5zM178.5 13.7c.7-.4 1-.7 1-1-.4.1-.7.3-1 1zM163 12.6c-.1.1-.2.1-.3.2.3-.1.3-.2.3-.2zM130.2 12c.7-.4 1.6-.3 2.4-.5-.7.2-1.9-.3-2.4.5zM226.1 11.4l-.7.6.8-.4zM218.6 12c-.3-.1-1.7.3-1.3.6.4-.3.9-.5 1.3-.6zM189.6 11.4l-.3.6.7-.5z"/>
</svg>

使用SVG创建文本下线划效果,除了直接引用一个.svg文件之外,还可以将SVG转换为Data Base64,比如下面这个示例:

上面示例其实和引用一个背景图片并无差异,但在某些场景下还是有一定的局限性,比如说,在鼠标悬浮状态下给改变颜色,添加动效等。不过,我们可以将SVG直接内联到HTML中,并且配合CSS的一些样式,灵活性就会更强一些。我们一起来看一个小示例:

<!-- HTML -->
<h1>
    W3cplus.com!!
    <svg fill="currentColor" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
        <use xlink:href="#line" href="#line"></use>
    </svg>
</h1>

<svg xmlns="http://www.w3.org/2000/svg" id="sr-only" viewBox="0 0 260 15.6">
    <symbol id="line" xmlins="http://www.w3.org/2000/svg">
        <path fill="currentColor" class="st0" d="M206.8 7.3l-.1.3c..." />
        <path fill="currentColor" class="st0" d="M260 6c-1-.6-4.7-1..." />
        <path fill="currentColor" class="st0" d="M58.1 11.1c-1 ..." />
    <symbol>
</svg>

// CSS
h1 {
    display: inline-flex;
    position: relative;
    color: #fff;
    transition: color .28s ease;
    cursor: pointer;
    
    svg {
        position: absolute;
        left: 0;
        right: 0;
        bottom: -4px;
        z-index: -1;
        pointer-events: none;
        height: 16px;
        width: 100%;
    }
    
    &:hover  {
        color: #f36;
    }
}

#sr-only {
    position: absolute;
    width: 0;
    height: 0;
}

效果如下:

在上例的基础上,我们还可以使用clip-path实现动画效果:

h1 {
    display: inline-flex;
    position: relative;
    color: #fff;
    transition: color .28s ease;
    cursor: pointer;
    font-size: 30px;
    
    svg {
        position: absolute;
        left: 0;
        right: 0;
        bottom: -4px;
        z-index: -1;
        pointer-events: none;
        height: 16px;
        width: 100%;
        transition: clip-path 275ms ease;
        clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);
    }
    
    &:hover  {
        color: #f36;
        
        svg {
            clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
        }
    }
}

将鼠标悬浮动文本上,你可以看到一个动效:

在SVG中,可以通过改变pathstroke-dasharraystroke-dashoffset等属性的值,实现动画效果,比如下面的示例,尝试着调整示例中的进度条滑块,可以看到SVG路径的动画效果:

如果你对这方面感兴趣的话,还可以阅读下面文章:

基于这个原理,我们就可以很好给文本下划线添加动画效果,前提是使用了SVG的path。比如下面这个示例:

h1 {
    display: inline-flex;
    position: relative;
    font-size: 64px;
    font-weight: bold;
    
    svg {
        position: absolute;
        left: 0;
        right: 0;
        bottom: -20px;
        height: 30px;
        z-index: -1;
        pointer-events: none;
    }
    
    path {
        animation: ani 6s ease alternate infinite;
    }
}

@keyframes ani {
    100% {
        stroke-dashoffset:1040;
    }
}

效果如下:

这一节中提到的几个示例都是围绕着SVG的<path>展开的,换句话说,如果你熟悉<path>的话,那么可以在此基础上实现任意你想要的下划线效果。如果你还不太熟悉的话,建议你花点时间阅读下面这几篇文章:

除了使用制图软件之外,还可以使用<path>的时候可以使用很多指令来绘制不同的线条,前提是你熟悉他的指令:

指令 参数 描述
M x y 起始点坐标 x y (Move to)
L x y 从当前点的坐标画直线到指定点的 x y 坐标 (Line to)
H x 从当前点的坐标画水平直线到指定的x轴坐标 (Horizontal line to)
V y 从当前点的座标画垂直直线到指定的y轴坐标 (Vertical line to)
C x1 y1 x2 y2 x y 从当前点的坐标画条贝塞尔曲线到指定点的x y坐标,其中 x1 y1x2, y2为控制点 (Curve)
S x2 y2 x y 从当前点的坐标画条反射的贝塞曲线到指定点的 x y坐标,其中x2 y2为反射的控制点(Smooth curve)
Q x1 y1 x y 从当前点的坐标画条反射二次贝塞曲线到指定点的x y坐标,其中x1 y1为控制点(Quadratic Bézier curve)
T x y 从当前点的坐标画条反射二次贝塞曲线到指定点的x y坐标,以前一个坐标为反射控制点(Smooth Quadratic Bézier curve)
A rx ry x-axis-rotation large-arc-flag sweep-flag x y 从当前点的坐标画个椭圆形到指定点的x y坐标,其中rx ry为椭圆形的x轴及y轴的半径,x-axis-rotation是弧线与x轴的旋转角度,large-arc-flag则设定1最大角度的弧线或是0最小角度的弧线,sweep-flag设定方向为1顺时针方向或0逆时针方向(Arc)
Z   关闭路径,将当前点坐标与第一个点的坐标连接起来(Closepath)

比如下面代码:

<svg width="400" height="35" xmlns="http://www.w3.org/2000/svg">
    <path id="pathItem"
        d="M5 5 Q 30 15 170 5"
        stroke="black"
        fill="transparent"
        stroke-width="7"
        stroke-linecap="round"/>
</svg>

其中d的值是<path>的数据,绘制了一条二次贝塞尔曲线(Quadratic Bézier curve)。其意思是”移动点1的坐标是(5, 5),然后在点(30,15)处添加曲线斜率,移动点2的坐标是(170,5)“。最终绘制出来的效果如下:

如果在制作图像中的话,上面的路径大致就是像下图那样:

除了使用Q指令之外,还可以使用C指令:

<svg width="400" height="35" xmlns="http://www.w3.org/2000/svg">
    <path id="pathItem"
        d="M0,0 C16,6 72,6 165,0"
        stroke="black"
        fill="transparent"
        stroke-width="7"
        stroke-linecap="round"/>
</svg>

效果如下:

对于如何使用<path>指令如何绘制线条,这里不做过多的探讨。你也可以使用 SVG Path Generator 生成。

有了这个基础之后,我在想:

使用SVG的<path>并配合一定的JavaScript脚本是否就可以创建随机的线条,从而实现给文本添加随机的下划线效果

记得前面的示例中,改变<path>stroke-dashoffsetstroke-dasharray属性的值可以让线条动起来,并且结合clip-path可以实现其他的动画效果,比如条线由短变长,由长变短:

其实在<path>中,还可以直接改变坐标点来调整路径的长短,比如:

<svg width="400" height="35" xmlns="http://www.w3.org/2000/svg">
    <path id="pathItem"
        d="M5 5 Q 30 15 170 5"
        stroke="black"
        fill="transparent"
        stroke-width="7"
        stroke-linecap="round"/>
</svg>

<svg width="400" height="35" xmlns="http://www.w3.org/2000/svg">
    <path id="pathItem"
        d="M5 5 Q 30 15 70 5"
        stroke="#f35"
        fill="transparent"
        stroke-width="7"
        stroke-linecap="round"/>
</svg>

你将看到效果如下:

两条路径的d数据非常的相似:

d="M5 5 Q 30 15 170 5"
d="M5 5 Q 30 15 70 5"

可以看出来,坐标2的x轴值发生了变化,由170变成了70。因此线条也就变短了。基于这个原理,可以使用JavaScript来控制这个x值,从而得给路径不同的d数据:

const pathItem = document.getElementById("pathItem");

input.addEventListener("change", handleUpdate);
input.addEventListener("mousemove", handleUpdate);

function handleUpdate(e) {
    let pathEnd = this.value;
    let newPath = `M5 5 Q 30 15 ${pathEnd} 5`;
    pathItem.setAttribute("d", newPath);
}

尝试着拖动下面进度条滑块,你可以看到SVG的路径长短将会发生变化:

使用JavaScript还可以动态创建贝塞尔曲线,比如:

有关于这方面更多的介绍可以阅读:

要彻底搞懂如何使用JavaScript绘制贝塞尔曲线是比较复杂的。我们还是回到我们自己的主题中来。在我们的示例中,不需要具备很深的知识,为什么呢?

刚才提到过,只需要几行JavaScript脚本就可以改变SVG的<path>d值,即改变路径。其实,我们还可以使用JavaScript让路径变成一个随机的路径,即<path>d的值是随机的,示例所需的脚本如下:

const moveYMin = 5;
const moveYMax = 12;

const curveXMin = 20;
const curveXMax = 100;

const curveYMin = 5;
const curveYMax = 20;

const endXMin = 50
const endXMax = 160

const endYMin = 5;
const endYMax = 10;

const button = document.getElementById("button");
const randomPath = document.getElementById("pathItem");

button.addEventListener("click", e => {
    let moveY = Math.floor(Math.random() * (moveYMax - moveYMin)) + moveYMin;
    let curveX = Math.floor(Math.random() * (curveXMax - curveXMin)) + curveXMin;
    let curveY = Math.floor(Math.random() * (curveYMax - curveYMin)) + curveYMin;
    let endX = Math.floor(Math.random() * (endXMax - endXMax)) + endXMax;
    let endY = Math.floor(Math.random() * (endYMax - endYMin)) + endYMin;
    let newPath = `M5 ${moveY} Q ${curveX} ${curveY} ${endX} ${endY}`;

    randomPath.setAttribute("d", newPath);

});

效果如下:

在上面的基础上再进行一下改造:

const createSVG = targetWidth => {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("width", targetWidth);
    svg.setAttribute("height", "20");

    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    let pathD = randomizePath(targetWidth);

    path.setAttribute("d", pathD);
    path.setAttribute("fill", "transparent");
    path.setAttribute("stroke", "lightgrey");
    path.setAttribute("stroke-width", "7");
    path.setAttribute("stroke-linecap", "round");

    svg.appendChild(path);

    return svg;
};

const randomizePath = targetWidth => {
    const moveYMin = 5;
    const moveYMax = 12;
    const curveXMin = 20;
    const curveXMax = targetWidth; /* Width of the targetElement */
    const curveYMin = 5;
    const curveYMax = 20;
    const endYMin = 5;
    const endYMax = 10;

    let moveY = Math.floor(Math.random() * (moveYMax - moveYMin)) + moveYMin;
    let curveX = Math.floor(Math.random() * (curveXMax - curveXMin)) + curveXMin;
    let curveY = Math.floor(Math.random() * (curveYMax - curveYMin)) + curveYMin;
    let endY = Math.floor(Math.random() * (endYMax - endYMin)) + endYMin;

    let newPath = `M5 ${moveY} Q ${curveX} ${curveY} ${targetWidth - 7} ${endY}`;

    return newPath;
};

const insertAfter = (el, referenceNode) => {
    referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling);
};

const targetEle = document.querySelector("h1");
const targetEleWidth = parseInt(targetEle.offsetWidth);
const svg = createSVG(targetEleWidth);
insertAfter(svg, targetEle);

效果如下:

每次刷新,SVG的<path>效果都会有所差异。如果你感兴趣的话,还可以在此基础上做更多的改造和优化。比如下面的教程就是这方面的相关介绍:

SVG和CSS Masking实现下划线效果

首先,如果你从未接触过CSS Masking的话,建议你先花时间阅读:

这里假设你对CSS Masking有一定的接触或了解。即使没有也不用太紧张,因为接下来的内容不会过于复杂。

首先,使用一个矢量制图软件,比如Sketch软件,快速画一条线(其实就是<path>)。怎么画,前面有提到过,这里有一点需要注意的是,为了让线条能重复,需要确保线条在水平方向可以无缝衔接。要做到这一点,只需要确保第一个点和最后一个点的y坐标值相同

导出SVG,并且对SVG进行优化,拿到最简洁的SVG代码:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 75">
    <path id="squiggle" d="M12 19.778C12 1.318 36.745-6.008 54.219 5.556c8.913 5.898 16.498 21.96 17.028 36.162.047 1.243.041 2.458-.016 3.642l-.024.41.485.132c6.082 1.634 11.768 2.9 16.315 3.61l.586.09c5.018.746 10.426 1.091 15.846 1.082l1.241-.009-.175-.216c-5.34-6.67-8.374-14.234-8.5-21.288L97 28.712c0-17.914 11.128-24.145 33.419-21.816 20.311 2.122 30.088 20.755 22.159 36.496-3.407 6.763-12.924 11.075-26.427 13.395l-.386.064.5.26c7.795 3.986 17.234 6.242 27.999 6.34l.736.003c7.47 0 12.604-2.367 16.657-7.101l.363-.431c2.922-3.54 4.959-7.632 8.263-15.958l1.487-3.786c3.53-8.983 5.894-13.9 9.46-18.657C196.01 11.14 202.088 6.918 210.058 5c17.363-4.179 28.412 6.79 32.24 25.786.224 1.117.421 2.252.59 3.397l.048.337.214.01c1.84.073 3.669.117 5.488.131l1.362.006v7.94c-1.817 0-3.644-.027-5.48-.084l-.888-.03.004.119c.366 11.224-1.754 21.867-5.821 25.652l-.181.163c-9.839 8.545-20.88 8.82-35.619.459-18.22-10.335-5.375-40.825 15.68-37.137 5.313.931 10.547 1.639 15.704 2.124l1.36.123-.145-.82-.161-.832c-3.048-15.125-10.496-22.518-22.51-19.627-6.078 1.463-10.587 4.596-14.295 9.543l-.405.55c-2.735 3.786-4.788 8.074-7.798 15.69l-1.083 2.762c-4.15 10.598-6.524 15.462-10.609 20.233-5.57 6.505-12.93 9.9-22.753 9.9-16.603 0-30.714-4.653-41.213-12.816l-.399-.314-.894.057c-8.445.501-17.051.247-24.233-.746l-.854-.122c-4.71-.702-10.583-1.978-16.88-3.642l-.695-.185-.068.22C66.356 64.55 57.219 71.217 42.596 71.393l-.596.003c-12.942 0-18.604-5.695-20.82-16.46l-.248-1.299c-.069-.381-.13-.744-.228-1.35l-.352-2.19c-.842-5.087-1.48-6.296-3.01-6.548l-.655-.103c-3.604-.529-8.965-.813-16.023-.835l-.667-.001.006-7.941 1.433.004c7.527.044 13.246.385 17.222 1.043 3.58.592 6.114 2.65 7.634 5.814l.205.44c.786 1.761 1.206 3.533 1.732 6.73l.545 3.353.126.69.129.657C30.526 60.722 33.352 63.454 42 63.454c11.048 0 17.56-4.226 20.075-11.764l.08-.248-.02-.005c-6.241-1.881-12.573-4.043-18.347-6.323l-.784-.312C23.514 37.002 12 28.946 12 19.778zm204.305 19.793c-12.522-2.194-20.467 16.666-10.32 22.421l.867.484c11.452 6.288 18.614 5.969 25.514-.025.33-.286.91-1.346 1.45-2.991.64-1.954 1.152-4.486 1.474-7.35.337-2.995.453-6.193.342-9.41l-.027-.667-.805-.063a204.58 204.58 0 01-17.052-2.152l-1.443-.247zM129.58 14.793l-1.075-.107C111.166 13.043 105 16.735 105 28.712c0 6.8 3.921 14.786 10.896 21.225l.204.185.254-.023c14.007-1.36 26.081-5.004 28.933-10.007l.135-.251c5.47-10.858-1.172-23.515-15.84-25.048zm-79.8-2.63C36.983 3.693 20 8.721 20 19.778c0 4.32 9.802 11.178 25.996 17.66 5.25 2.101 11.031 4.11 16.777 5.886l.505.155v-.267c0-.263-.005-.529-.012-.797l-.013-.404c-.442-11.828-6.897-25.497-13.472-29.848z"/>
</svg>

线条的效果如下:

虽然我们可以通过fill来修改SVG的<path>的颜色:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 75">
    <path id="squiggle" fill="#f36" d="M12 19.778C12 .../>
</svg>

除此之外,还可以通过CSS来设置fill值改变路径颜色:

path {
    fill: #f36;
}

除此之外,可以使用CSS Masking让它更为灵活。通过mask来让事情变得更为灵活:

h1 {
    display: inline-flex;
    position: relative;
    padding-bottom: 8vh;
    font-size: 20vh;
    cursor: pointer;
    transition: all .2s ease;
    
    &:hover {
        color: #f36;
    }

    &::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: 4vh;
        height: 6vh;
        background-color: currentColor;
        mask-image: url("/repeater.svg#squiggle");
    }
}

效果如下:

在上面的基础上,我们只需要调整h1color就可以得到不一样的效果(伪元素的background-color设置了currentColor):

如果将背景色调整渐变色效果就是另一种风彩:

甚至我们可以调整mask的其他属性值,比如mask-sizemask-position得到其他效果,还可以结合@keyframes实现一些动效:

上面我们看到的效果是实现下划线效果。该技术方案还可以用于一些不规则的Banner设计上,如下图所示:

是不是类似水波纹,实现水波动效也变得更容易。感兴趣的同学,不仿自己写个效果看看,验证一下也是不错的嘛。

小结

虽然CSS的text-decoration特性新增了不少属性,可以帮助我们给文本添加更丰富的下划线效果,但要实现一些具有创意的效果还是有很大的局限性。不过随着Web新技术的到来以及浏览器的变革,我们也具备更多的技术方案,比如文章中提到的,使用clip-pathmask和SVG等实现不同的下划线效果。

虽然文章中聊的都是给文本添加下划线效果,但其技术也可以运用于其他的场景。感兴趣的同学不仿一试,如果你在这方面有更好的建议和经验,欢迎在下面的评论中与我们一起分享。