Skip to content

Gallery / interactive / Emoji mood slider

Emoji mood slider

Range slider morphing a face from sad to happy via one custom property

interactive advanced interactiveslideremojicustom-propertiesjs
Live preview

Techniques: calc() · clamp() · custom-property-data-flow · scaleY-mouth-flip · hsl()

Customizeupdates preview, code & clipboard
140px
0.25s
Code snippets/interactive/slider-emoji-mood.html
<!--
  ============================================================
  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>

More interactive

\n","slug":"slider-emoji-mood","vars":[{"name":"--sg-size","unit":"px","initial":"140px"},{"name":"--sg-duration","unit":"s","initial":"0.25s"}]}