Emoji mood slider
Range slider morphing a face from sad to happy via one custom property
Techniques: calc() · clamp() · custom-property-data-flow · scaleY-mouth-flip · hsl()
<!--
============================================================
SVGarden — https://www.svgarden.dev
Author : Simon-Pierre Boucher
Contact: contact@spboucher.ai
File : snippets/interactive/slider-emoji-mood.html
Desc : Range slider morphing a face from sad to happy via one custom property
============================================================
-->
<div class="sg-slider-emoji-mood" style="--sg-size:140px; --sg-duration:0.25s; --sg-mood:50;">
<svg viewBox="0 0 64 64" role="img" aria-label="Mood face">
<title>Mood face</title>
<circle class="sg-slider-emoji-mood-face" cx="32" cy="32" r="26" />
<circle class="sg-slider-emoji-mood-pupil" cx="23" cy="27" r="2.8" />
<circle class="sg-slider-emoji-mood-pupil" cx="41" cy="27" r="2.8" />
<line class="sg-slider-emoji-mood-brow" x1="19" y1="19.5" x2="27" y2="19.5" />
<line class="sg-slider-emoji-mood-brow" x1="37" y1="19.5" x2="45" y2="19.5" />
<g class="sg-slider-emoji-mood-mouth">
<path d="M22 41 Q 32 51 42 41" vector-effect="non-scaling-stroke" />
</g>
</svg>
<input class="sg-slider-emoji-mood-slider" type="range" min="0" max="100" value="50" aria-label="Mood, 0 sad to 100 happy" />
</div>
<script>
document.querySelectorAll('.sg-slider-emoji-mood:not([data-sg-init])').forEach(function (root) {
root.setAttribute('data-sg-init', '');
var slider = root.querySelector('.sg-slider-emoji-mood-slider');
slider.addEventListener('input', function () {
root.style.setProperty('--sg-mood', slider.value);
});
});
</script>
<style>
.sg-slider-emoji-mood {
width: var(--sg-size);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.7rem;
}
.sg-slider-emoji-mood svg {
display: block;
width: 100%;
}
.sg-slider-emoji-mood-face {
fill: hsl(calc(var(--sg-mood) * 1.2) 72% 62%);
transition: fill var(--sg-duration) ease;
}
.sg-slider-emoji-mood-pupil {
fill: rgba(25, 25, 35, 0.85);
}
.sg-slider-emoji-mood-brow {
stroke: rgba(25, 25, 35, 0.85);
stroke-width: 2.2;
stroke-linecap: round;
transform-box: fill-box;
transform-origin: center;
transition: transform var(--sg-duration) ease;
transform: rotate(clamp(-14deg, calc((50 - var(--sg-mood)) * 0.28deg), 14deg));
}
.sg-slider-emoji-mood-brow:nth-of-type(2) {
transform: rotate(clamp(-14deg, calc((var(--sg-mood) - 50) * 0.28deg), 14deg));
}
.sg-slider-emoji-mood-mouth {
transform-box: fill-box;
transform-origin: center;
transition: transform var(--sg-duration) ease;
/* +0.04 epsilon: a true scaleY(0) is a singular matrix and vanishes */
transform: scaleY(calc((var(--sg-mood) - 50) / 50 + 0.04));
}
.sg-slider-emoji-mood-mouth path {
fill: none;
stroke: rgba(25, 25, 35, 0.85);
stroke-width: 2.6;
stroke-linecap: round;
}
.sg-slider-emoji-mood-slider {
width: 90%;
accent-color: hsl(calc(var(--sg-mood) * 1.2) 65% 48%);
}
</style>