<div class="volume">
<svg class="shape" viewBox="0 0 64 88">
<polyline points="60 4 36 24 4 24 4 64 36 64 60 84"></polyline>
</svg>
<svg class="first" viewBox="0 0 20 88"></svg>
<svg class="second" viewBox="0 0 20 60"></svg>
<svg class="third" viewBox="0 0 20 60"></svg>
</div>
<!-- dribbble -->
<a class="dribbble" href="https://dribbble.com/shots/7707657-Volume-Toggle" target="_blank"><img src="https://cdn.dribbble.com/assets/dribbble-ball-mark-2bd45f09c2fb58dbbfb44766d5d1d07c5a12972d602ef8b32204d28fa3dda554.svg" alt=""></a>
.volume {
--line: #275EFE;
--line-width: 7px;
position: relative;
cursor: pointer;
padding-right: 48px;
-webkit-tap-highlight-color: transparent;
svg {
display: block;
fill: none;
stroke: var(--line);
stroke-width: var(--line-width);
stroke-linecap: round;
stroke-linejoin: round;
&:not(.shape) {
--scale: 1;
--top: 0;
--left: 50px;
--height: 88px;
position: absolute;
top: var(--top);
left: var(--left);
width: 20px;
height: var(--height);
transform: scaleY(var(--scale));
}
&.shape {
width: 64px;
height: 88px;
}
&.second,
&.third {
--top: 14px;
--height: 60px;
}
&.second {
--left: 72px;
--scale: .84;
}
&.third {
--left: 92px;
--scale: 1.16;
}
}
}
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;
justify-content: center;
align-items: center;
flex-direction: column;
position: relative;
background: #E8EBFB;
.dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
img {
display: block;
height: 28px;
}
}
}
View Compiled
$('.volume').each(function(e) {
let volume = $(this),
svg = volume.find('svg:not(.shape)'),
proxies = [];
svg.each(function(i) {
let svg = $(this);
proxies[i] = new Proxy({ x: null }, {
set(target, key, value) {
target[key] = value;
if(target.x !== null) {
svg.html(getPath(target.x, svg.height()));
}
return true;
},
get(target, key) {
return target[key];
}
});
proxies[i].x = 10;
});
proxies[1].x = 12;
proxies[2].x = 15;
let timeline = new TimelineMax({
paused: true
}).to(svg.eq(1), .4, {
x: 8
}).to(svg.eq(1), .3, {
x: -22,
scaleY: 1,
ease: Power3.easeOut
}).to(svg.eq(1), .3, {
x: 20
}).to(svg.eq(1), .8, {
rotation: 135,
ease: Elastic.easeOut.config(1, .4)
}, .9);
timeline.to(proxies[1], .2, {
x: 6
}, .5).to(proxies[1], .2, {
x: 10
}, .7).to(proxies[2], .2, {
x: 6
}, .5).to(proxies[2], .2, {
x: 10
}, .7);
timeline.to(svg.eq(2), .4, {
x: 8
}, 0).to(svg.eq(2), .3, {
x: -42,
scaleY: 1,
ease: Power3.easeOut
}, .4).to(svg.eq(2), .3, {
x: 0
}, .7).to(svg.eq(2), .8, {
rotation: 225,
ease: Elastic.easeOut.config(1, .4)
}, .9);
volume.on('click touch', e => {
TweenMax.to(proxies[0], volume.hasClass('muted') ? .12 : .2, {
x: 4,
repeat: 1,
yoyo: true
}).delay(volume.hasClass('muted') ? .95 : .5);
volume.hasClass('muted') ? timeline.reverse() : timeline.play();
volume.toggleClass('muted');
})
});
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, height) {
let smoothing = .2,
points = [
[10, 4],
[update, height / 2],
[10, height - 4]
],
d = points.reduce((acc, point, i, a) => i === 0 ? `M ${point[0]},${point[1]}` : `${acc} ${getPoint(point, i, a, smoothing)}`, '');
return `<path d="${d}" />`;
}
This Pen doesn't use any external CSS resources.