<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>


<hr>

<p class="ishadeed">Read the article on <a href="https://ishadeed.com/article/custom-underline-svg/">ishadeed.com</a>. <a href="https://github.com/shadeed/underliner">Github</a></p>
body {
	padding: 1rem;
	font-family: 'IBM Plex Sans', sans-serif;
}

svg {
	display: block;
}

.group {
	//outline: 1px solid lightgrey;
	padding: 1rem;
	margin-bottom: 2.5rem;
}

.c-nav {
  &:not(:last-child) {
    margin: 1rem 0 2rem;
  }

	@media (min-width: 900px) {
		display: flex;
		justify-content: center;
	}
}

.c-nav__item {
	margin-right: 1.5rem;
	font-size: 1.5rem;

	a {
		display: inline-block;
		text-decoration: none;
		color: #000;
		margin-bottom: 4px;
		transition: 0.4s;
	}

	svg {
		pointer-events: none;
        transition: 0.5s;
        //outline: solid 1px lightgrey;
	}

	path {
        transition: stroke-dasharray 0.5s, stroke-dashoffset 0.5s, opacity 0.5s;
        
        &:last-child {
            opacity: 0.2;
        }
	}

	a {
		&:hover,
		&:focus {
			color: #00bc9b;
		}
	}

	a:hover + svg,
	a:focus + svg {
		opacity: 1;

		path {
			stroke-dashoffset: 0;	
		}
	}
}

#path1 {
	opacity: 0.25;
}

.c-form {
	max-width: 500px;
	margin: 0 auto;

	legend {
		margin-bottom: 1rem;
		font-weight: bold;
	}

	label {
		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;
  
  a {
    color: #3563D9;
  }
}
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");

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.