Underliner是一款基于SVG的超链接鼠标悬停下划线动画特效。Underliner插件可以在鼠标hover或悬停在指定的超链接元素上面时,以动画的方式显示预定义的SVG元素。
使用方法
HTML结构
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 100 100"> <defs> <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#00bc9b" /> <stop offset="100%" stop-color="#5eaefd" /> </linearGradient> <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#5eaefd" /> <stop offset="100%" stop-color="#00bc9b" /> </linearGradient> </defs> </svg> <div class="group"> <ul class="c-nav underliner"> <li class="c-nav__item"><a href="#">News & Politics</a></li> <li class="c-nav__item"><a href="#">Culture</a></li> <li class="c-nav__item"><a href="#">Technology</a></li> <li class="c-nav__item"><a href="#">Business</a></li> <li class="c-nav__item"><a href="#">Human Interest</a></li> </ul> <ul class="c-nav c-nav-2 underliner-small"> <li class="c-nav__item"><a href="#">Works</a></li> <li class="c-nav__item"><a href="#">Articles</a></li> <li class="c-nav__item"><a href="#">About</a></li> <li class="c-nav__item"><a href="#">Contact Me</a></li> <li class="c-nav__item"><a href="#">My Books</a></li> </ul> </div>
CSS样式
ul li { list-style: none; } svg { display: block; } .group { padding: 1rem; margin-bottom: 2.5rem; } .c-nav:not(:last-child) { margin: 1rem 0 2rem; } @media (min-width: 900px) { .c-nav { display: flex; justify-content: center; } } .c-nav__item { margin-right: 1.5rem; font-size: 1.5rem; } .c-nav__item a { display: inline-block; text-decoration: none; color: #000; margin-bottom: 4px; transition: 0.4s; } .c-nav__item svg { pointer-events: none; transition: 0.5s; } .c-nav__item path { transition: stroke-dasharray 0.5s, stroke-dashoffset 0.5s, opacity 0.5s; } .c-nav__item path:last-child { opacity: 0.2; } .c-nav__item a:hover, .c-nav__item a:focus { color: #00bc9b; } .c-nav__item a:hover + svg, .c-nav__item a:focus + svg { opacity: 1; } .c-nav__item a:hover + svg path, .c-nav__item a:focus + svg path { stroke-dashoffset: 0; } #path1 { opacity: 0.25; } .c-form { max-width: 500px; margin: 0 auto; } .c-form legend { margin-bottom: 1rem; font-weight: bold; } .c-form label { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .c-form__item { margin-bottom: 1rem; } .c-nav-2 .c-nav__item { font-size: 1rem; } hr { border: 0; height: 1px; background: lightgrey; } .ishadeed { text-align: center; } .ishadeed a { color: #3563D9; }
javascript
class Underliner { constructor(selector, color1, color2, thickness1, thickness2, strokeLinecap, rtl) { this.links = document.querySelectorAll(selector) this.fill = 'transparent'; this.color1 = color1; this.color2 = color2; this.thickness1 = thickness1; this.thickness2 = thickness2; this.strokeLinecap = strokeLinecap; this.rtl = rtl; this.init(); } init() { let self = this; self.links.forEach(function (link) { let linkWidth = parseInt(link.offsetWidth); let svg = self.createSVG(linkWidth); self.insertAfter(svg, link); }); } setPath(pathD, color, thickness, strokeLinecap) { const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", pathD); path.setAttribute("fill", this.fill); path.setAttribute("stroke", color); path.setAttribute("stroke-width", thickness); path.setAttribute("stroke-linecap", strokeLinecap); path.setAttribute("stroke-dasharray", path.getTotalLength() + 10); path.setAttribute("stroke-dashoffset", path.getTotalLength() + 10); return path; } randomizePath(linkWidth) { let moveYMin = 5; let moveYMax = 12; let curveXMin = 15; let curveXMax = linkWidth; /* Width of the link */ let curveYMin = 7; let curveYMax = linkWidth * 0.12; /* Making the quadratic propotional to the link width */ //let curveYMax = 20 let endYMin = 5; let endYMax = 11; 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; return `M5 ${moveY} Q ${curveX} ${curveY} ${linkWidth - 7} ${endY}` } createSVG(linkWidth) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", linkWidth); svg.setAttribute("height", "35"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path"); let pathD = this.randomizePath(linkWidth); let pathD2 = this.randomizePath(linkWidth); if(this.rtl === true) { pathD = this.reverseMe(pathD); pathD2 = this.reverseMe(pathD2); } svg.appendChild(this.setPath(pathD, this.color1, this.thickness1, this.strokeLinecap)); svg.appendChild(this.setPath(pathD2, this.color2, this.thickness2, this.strokeLinecap)); svg.setAttribute("focusable", false); return svg; } reverseMe(path) { /* Regex functions borrwed from https://github.com/krispo/svg-path-utils/blob/master/src/svg-path-utils.js */ let pathOperators = path.replace(/[\d,\-\s]+/g, '').split(''); let pathNums = path.replace(/[A-Za-z,]+/g, ' ').trim().replace(/\s\s+/g, ' ').split(' '); return `${pathOperators[0]} ${pathNums[4]} ${pathNums[5]} ${pathOperators[1]} ${pathNums[2]} ${pathNums[3]} ${pathNums[0]} ${pathNums[1]}`; } // https://plainjs.com/javascript/manipulation/insert-an-element-after-or-before-another-32/ insertAfter(el, referenceNode) { referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling); } } let test = new Underliner(".underliner a", "url(#gradient)", "url(#gradient2)", 7, 12, "round", false); let test2 = new Underliner(".underliner-small a", "url(#gradient)", "url(#gradient2)", 3, 6, "round");
Underliner插件的codepen网址为:https://codepen.io/shadeed/pen/OeNbMO