.button {
--background: #2B3044;
--text: #fff;
--icon: #fff;
--icon-success: #16BF78;
display: flex;
outline: none;
border: 0;
padding: 0 22px 0 16px;
min-width: 147px;
overflow: hidden;
cursor: pointer;
border-radius: 9px;
background: var(--background);
transition: transform .2s;
&:active {
transform: scale(.95);
}
ul {
margin: 0;
padding: 13px 0;
list-style: none;
text-align: center;
position: relative;
backface-visibility: hidden;
font-size: 16px;
font-weight: 500;
line-height: 25px;
color: var(--text);
li {
&:not(:first-child) {
top: var(--t, 13px);
left: 0;
position: absolute;
}
&:nth-child(2) {
--t: 64px;
}
&:nth-child(3) {
--t: 115px;
}
}
}
.icon {
width: 24px;
height: 24px;
position: relative;
display: inline-block;
vertical-align: top;
margin: 14px 10px 0 0;
transform: translateY(calc(var(--y, 0) * 1px));
svg {
width: var(--w, 14px);
height: var(--h, 15px);
display: block;
fill: none;
stroke: var(--s, var(--icon));
stroke-width: var(--sw, 2);
stroke-linejoin: round;
stroke-linecap: round;
}
& > svg,
div {
left: var(--l, 7px);
top: var(--t, 2px);
position: absolute;
}
& > svg {
transform: translateY(calc(var(--y, 0) * 1px));
polyline,
line {
stroke-dasharray: var(--a, 12px);
stroke-dashoffset: var(--o, 0);
stroke: var(--s, var(--icon));
transition: stroke-dashoffset var(--d, .15s), stroke .4s;
}
polyline {
--d: .25s;
--a: 17px;
--o: 5.5px;
}
}
div {
--w: 24px;
--h: 24px;
--l: 0;
--t: 8px;
--sw: 1.5;
}
}
&.loading {
ul {
animation: text 1750ms linear forwards 100ms;
}
&.complete {
.icon {
svg {
line {
--o: 12px;
}
polyline {
--o: 0;
--s: var(--icon-success);
}
}
}
}
}
}
@keyframes text {
18%,
82% {
transform: translateY(-100%);
}
100% {
transform: translateY(-200%);
}
}
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: inherit;
&:before,
&:after {
box-sizing: inherit;
}
}
// Center & dribbble
body {
min-height: 100vh;
display: flex;
font-family: 'Roboto', Arial;
justify-content: center;
align-items: center;
flex-direction: column;
background: #F6F8FF;
.dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
img {
display: block;
height: 28px;
}
}
.twitter {
position: fixed;
display: block;
right: 64px;
bottom: 14px;
svg {
width: 32px;
height: 32px;
fill: #1da1f2;
}
}
}
View Compiled
const $ = (s, o = document) => o.querySelector(s);
const $$ = (s, o = document) => o.querySelectorAll(s);
$$('.button').forEach(button => {
let icon = $('.icon', button),
arrow = $('.icon > svg', button),
line = $('.icon div svg', button),
svgPath = new Proxy({
y: null
}, {
set(target, key, value) {
target[key] = value;
if(target.y !== null) {
line.innerHTML = getPath(target.y, .25, null);
}
return true;
},
get(target, key) {
return target[key];
}
});
svgPath.y = 12;
button.addEventListener('click', e => {
if(!button.classList.contains('loading')) {
button.classList.add('loading');
gsap.timeline({
repeat: 2
}).to(svgPath, {
y: 17,
duration: .17,
delay: .03
}).to(svgPath, {
y: 12,
duration: .3,
ease: Elastic.easeOut.config(1, .35)
});
gsap.timeline({
repeat: 2,
repeatDelay: .1,
onComplete() {
gsap.to(arrow, {
'--y': -17.5,
duration: .4
});
setTimeout(() => button.classList.add('complete'), 200);
}
}).to(arrow, {
'--y': 9,
duration: .2
}).to(arrow, {
'--y': -9,
duration: .2
});
gsap.timeline().to(icon, {
y: 4,
duration: .2
}).to(icon, {
y: 8,
duration: .2,
delay: .2
}).to(icon, {
y: 12,
duration: .2,
delay: .2
}).to(icon, {
y: 18,
duration: .2,
delay: .2
});
}
e.preventDefault();
});
});
function getPoint(point, i, a, smoothing) {
let cp = (current, previous, next, reverse) => {
let p = previous || current,
n = next || current,
o = {
length: Math.sqrt(Math.pow(n[0] - p[0], 2) + Math.pow(n[1] - p[1], 2)),
angle: Math.atan2(n[1] - p[1], n[0] - p[0])
},
angle = o.angle + (reverse ? Math.PI : 0),
length = o.length * smoothing;
return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length];
},
cps = cp(a[i - 1], a[i - 2], point, false),
cpe = cp(point, a[i - 1], a[i + 1], true);
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
}
function getPath(update, smoothing, pointsNew) {
let points = pointsNew ? pointsNew : [
[2, 12],
[12, update],
[22, 12]
],
d = points.reduce((acc, point, i, a) => i === 0 ? `M ${point[0]},${point[1]}` : `${acc} ${getPoint(point, i, a, smoothing)}`, '');
return `<path d="${d}" />`;
}