Volume Slider
UXtopia
by sm1215
HTML
<div id="volume-slider">
<svg id="volume-icon" class="volume-icon" viewBox="-1 0 33 32">
<defs>
<mask id="circle-mask" x="-1" y="0" width="33" height="32">
<circle cx="-1" cy="16" r="33" fill="white" id="circle-mask-shape" />
</mask>
<mask id="volume-mask" x="-1" y="0" width="33" height="32">
<path d="M22.485 25.985c-0.384 0-0.768-0.146-1.061-0.439-0.586-0.586-0.586-1.535 0-2.121 4.094-4.094 4.094-10.755 0-14.849-0.586-0.586-0.586-1.536 0-2.121s1.536-0.586 2.121 0c2.55 2.55 3.954 5.94 3.954 9.546s-1.404 6.996-3.954 9.546c-0.293 0.293-0.677 0.439-1.061 0.439v0zM17.157 23.157c-0.384 0-0.768-0.146-1.061-0.439-0.586-0.586-0.586-1.535 0-2.121 2.534-2.534 2.534-6.658 0-9.192-0.586-0.586-0.586-1.536 0-2.121s1.535-0.586 2.121 0c3.704 3.704 3.704 9.731 0 13.435-0.293 0.293-0.677 0.439-1.061 0.439zM13 30c-0.26 0-0.516-0.102-0.707-0.293l-7.707-7.707h-3.586c-0.552 0-1-0.448-1-1v-10c0-0.552 0.448-1 1-1h3.586l7.707-7.707c0.286-0.286 0.716-0.372 1.090-0.217s0.617 0.519 0.617 0.924v26c0 0.404-0.244 0.769-0.617 0.924-0.124 0.051-0.254 0.076-0.383 0.076z" fill="white" mask="url(#circle-mask)"></path>
</mask>
<linearGradient id="grad-1" x1="0" x2="1" y1="0" y2="0">
<stop offset="20%" stop-color="#9a88aa" />
<stop offset="100%" stop-color="#6e33a5" />
</linearGradient>
</defs>
<path class="volume-icon-bg" fill="#cbc8ce" d="M22.485 25.985c-0.384 0-0.768-0.146-1.061-0.439-0.586-0.586-0.586-1.535 0-2.121 4.094-4.094 4.094-10.755 0-14.849-0.586-0.586-0.586-1.536 0-2.121s1.536-0.586 2.121 0c2.55 2.55 3.954 5.94 3.954 9.546s-1.404 6.996-3.954 9.546c-0.293 0.293-0.677 0.439-1.061 0.439v0zM17.157 23.157c-0.384 0-0.768-0.146-1.061-0.439-0.586-0.586-0.586-1.535 0-2.121 2.534-2.534 2.534-6.658 0-9.192-0.586-0.586-0.586-1.536 0-2.121s1.535-0.586 2.121 0c3.704 3.704 3.704 9.731 0 13.435-0.293 0.293-0.677 0.439-1.061 0.439zM13 30c-0.26 0-0.516-0.102-0.707-0.293l-7.707-7.707h-3.586c-0.552 0-1-0.448-1-1v-10c0-0.552...
SCSS
body{
background: #f9f9f9;
}
#volume-slider{
align-items: center;
display : flex;
margin : 3em auto;
padding : 30px 0;
width : 265px;
}
.volume-icon{
cursor : pointer;
height : 60px;
transform : rotate(-0deg);
transform-origin: center left;
width : 60px;
.volume-icon-bg{
fill : #cbc8ce;
transition: fill 300ms;
}
&:hover .volume-icon-bg{
fill: #b6aebf;
}
}
#circle-mask-shape{
transform : scale(0);
transform-origin: center center;
}
.volume-track{
background-color: #e1dee5;
border-radius : 3px;
flex : 1;
height : 4px;
margin-left : 5px;
position : relative;
}
.volume-indicator{
background-color: #6e33a5;
border-radius : 50%;
height : 12px;
left : 0;
position : absolute;
top : -4px;
transition : visibility 100ms, opacity 100ms;
width : 12px;
}
Babel + JSX
class VolumeSlider{
constructor(){
this.slider = document.getElementById('volume-slider');
this.icon = document.getElementById('volume-icon');
this.indicator = document.getElementById('volume-indicator');
this.shape = document.getElementById('circle-mask-shape');
this.input = document.getElementById('volume-input');
this._lock = false;
this._charging = false;
this._charge = 0;
this._volume = 0;
this.input.value = this._volume;
this.icon.addEventListener('mousedown', () => { this.charge(); });
this.icon.addEventListener('mouseup', () => { this.release(this._charge); });
this.icon.addEventListener('touchstart', () => { this.charge(); });
this.icon.addEventListener('touchend', () => { this.release(this._charge); });
}
/**
* Begin charge cycle
*/
charge(){
if(this._lock){ return false; }
this._lock = true;
// Reset
this._charge = 0;
this._charging = true;
// Hide indicator
this.indicator.style.visibility = 'hidden';
this.indicator.style.opacity = '0';
/**
* Charge loop
*/
let cycle = () => {
if(this._charging && ++this._charge < 100){
requestAnimationFrame(() => {
cycle();
});
}
// Update icon styles
this.shape.style.transform = `scale(${this._charge / 100})`;
this.icon.style.transform = `rotate(${-0.35 * this._charge}deg)`;
};
setTimeout(() => cycle(), 100);
}
/**
* Release and fire based on charge
* @param {float} charge
*/
release(charge){
// Reset charge animation
this._charging = false;
requestAnimationFrame(() => { this.shape.style.transform = `scale(0)`; });
// Animation vars
let y_cap = charge * 0.35,
y_start = -0.3 * charge,
x_cap = charge * 2,
x_start = -10,
duration = 20 + (4 * charge),
start = new Date().getTime(),
volume =...