<a class="design-reference" href="//100daysui.com/#cbp=ajax/shot-90" target="_blank">100 Days UI Challenge - Day 90</a>

<div class="component">

  <aside>
    <div class="preamp">
      <label>preamp</label>
    </div>
  </aside>

  <main>
    <div class="presets">
      <label>Presets:</label>
      <select name="Custom" onchange="app.selectPreset(this)">
        <option value="custom">Custom</option>
        <option value="rock">Rock</option>
        <option value="pop">Pop</option>
        <option value="pop">Classical</option>
        <option value="pop">Disco</option>
      </select>
      <button class="reset" onclick="app.reset()">Reset</button>
    </div>

    <div class="sliders">
      
      <svg preserveAspectRatio="none" viewBox="0 0 140 100">
        <path d="" class="line-shadow"></path>
        <path d="" class="line"></path>        
      </svg>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

      <div class="range-slider">
        <input type="range" orient="vertical" min="0" max="100" />
        <div class="range-slider__bar"></div>
        <div class="range-slider__thumb"></div>
      </div>

    </div>
  </main>    
</div>

// If those values are updated, remember to update in JS
$slider-thumb-size: 20px;
$slider-height: 300px;

$slider-track-thickness: 4px;
$slider-width: 80px;
$slider-width-device-small: 40px;
$color-theme: #3D3D4A;
$color-track: #343440;
$color-text: lighten(#737383,10%);
$border-radius: 10px;

@mixin device-bigger {
  @media (min-width: 800px) { @content; }
}

// The input range mixin code is based on Ana Tudor's pen codepen.io/thebabydino/pen/pvLPOQ

@mixin track {
  border: none;    
  background: $color-track;
  width: $slider-track-thickness;
  border-color: $color-track;
  border-radius: 10px;
  box-shadow: 0 0 0 2px $color-theme;
}

@mixin thumb {
  position: relative;
  // Increase hitbox
  width: $slider-thumb-size*2; 
  height: $slider-thumb-size*2;  
  opacity: 0; // Hide the native styling
}

*{
  outline: none;
}
*, *:before,*:after{
  box-sizing: border-box;
}

html,
body{
  height: 100%;  
}
body{       
  margin: 0;
  padding: 10px;     
  min-height: 400px;  
  font-family: sans-serif;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  @media(min-height: 500px){
    align-items: center;
  }
  font: 16px sans-serif;
  background: radial-gradient(#B3B1CB,#E1DEFE);
}

.component{  
  position: relative;
  color: white; 
  background-color: $color-theme;    
  border-radius: $border-radius;
  box-shadow: 0px 20px 40px rgba(black,.5),0px -2px 40px rgba(black,.3);
  //box-shadow:0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
  min-width: 280px;
  display: flex;
  flex-direction: column;
  @include device-bigger {
    flex-direction: row;
  }  
  &:before,&:after{
    content:'';
    background-color: transparent;
    position:absolute;
    z-index: -1;
    box-shadow: 0 20px 20px rgba(black,.3);
    top:100%;
    bottom:-5px;
    left:8%;
    right:8%;    
    border-radius: 50%;
  }
  &:after{
    box-shadow: 0 25px 20px rgba(black,.6);
    left:12%;
    right:12%;      
  }

  aside{    
    // outline: 1px dashed deeppink;  // debug  
    position: relative;
    display: block;
    background: #373641;    
    border-top-left-radius: $border-radius;
    border-top-right-radius: $border-radius;

    @include device-bigger {
      border-top-right-radius: 0;
      border-bottom-left-radius: $border-radius;
      min-width: 140px;
    }    

    .preamp{
      height: 70px;      
      @include device-bigger {
        height: 100px;
      }

      display: flex;
      justify-content: center;
      align-items: center;
      > label{
        color: #DEDFE4;
        text-transform: uppercase;  
        display: block;
        font-weight: 700;
      }  
    }

  }
  main{
    position: relative;
    display: block;    
    padding-bottom: 50px;
    @include device-bigger {
      padding-left: 20px;
      padding-right: 20px;  
    }

    .presets{
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 10px;
      padding-left: calc(#{$slider-width-device-small}/2 - .5em);
      height: 100px;      
      @include device-bigger {
        padding-left: calc(#{$slider-width}/2 - .5em);
        justify-content: flex-start;
      }      
      color: $color-text;
      font-weight: 700;
      border-color: #4F4F62;
      background: transparent;      
      > label{
        display: inline-block;
        margin-right: 20px;        
      }
      > select{
        -webkit-appearance: none;
        border-radius: 8px;
        border: 2px solid currentColor;
        max-width: 200px;        
        padding: 4px;
        @include device-bigger {
          min-width: 200px;
          padding-left: 10px;
        }
        color: inherit;
        background: transparent;
        border-color: inherit;

        height: 30px;
        margin-right: 10px;

        option{
          background-color: $color-theme;
        }
      }
      > button{
        height: 30px;                
        @include device-bigger {
          min-width: 80px;
        }
        border-radius: 8px;        
        background: transparent;
        color: inherit;
        border: 2px solid currentColor;
        border-color: inherit;
        padding: 4px 10px;
        cursor: pointer;
        outline: none;
      }
    }

    .sliders{
      position: relative;
      display: inline-block;  

      .range-slider {    
        display: inline-block;
        width: $slider-width-device-small;
        @include device-bigger {
          width: $slider-width;
        }
        position: relative;          
        height: $slider-height;        
        float: left;
        &::after{
          position: absolute;
          bottom: -24px;
          left: calc(50% - 2em);
          font-size: 80%;
          color: $color-text;
          content: '32';
          width: 4em;
          text-align: center;
        }
        &:nth-child(2)::after{
          content: '32';
        }
        &:nth-child(3)::after{
          content: '64';
        }
        &:nth-child(4)::after{
          content: '128';
        }
        &:nth-child(5)::after{
          content: '256';
        }
        &:nth-child(6)::after{
          content: '512';
        }
        &:nth-child(7)::after{
          content: '1K';
        }
        &:nth-child(8)::after{
          content: '2K';
        }

        &__thumb{          
          opacity: 1;
          position: absolute;
          left: $slider-width-device-small/2 - $slider-thumb-size/2;
          @include device-bigger {
            left: $slider-width/2 - $slider-thumb-size/2;
          }
          width: $slider-thumb-size;
          height: $slider-thumb-size;
          line-height: $slider-thumb-size;
          background-color: white;          
          color: #8376FF;
          text-align: center;  
          font-size: 40%;
          box-shadow: 0 0 2px #373641;
          border-radius: 50%;
          pointer-events: none;
          cursor: pointer;
          z-index: 2;
        }
        &__bar{
          left: $slider-width-device-small/2 - $slider-track-thickness/2;
          @include device-bigger {
            left: $slider-width/2 - $slider-track-thickness/2;
          }
          bottom: 0;
          position: absolute;
          background: linear-gradient(#9791B8,#8376FF);
          pointer-events: none;
          width: $slider-track-thickness;    
          border-radius: 10px;
          opacity: 1;
        }

        input[type=range][orient=vertical]
        {
          //outline: 1px dashed white; // debug
          position: relative;  
          margin: 0;
          height: 100%;  
          width: 100%;
          display: inline-block;  
          position: relative;    
          writing-mode: bt-lr; // IE
          -webkit-appearance: slider-vertical;  // webkit      
          &::-webkit-slider-runnable-track, 
          &::-webkit-slider-thumb {
            -webkit-appearance: none;   
          }
          &::-webkit-slider-runnable-track {
            @include track;
          }
          &::-moz-range-track {
            @include track;
          }
          &::-ms-track {
            @include track;
            color: transparent;
            height: 100%;  
          }
          &::-ms-fill-lower, 
          &::-ms-fill-upper, 
          &::-ms-tooltip {
            display: none;
          }
          &::-webkit-slider-thumb {  
            left: -$slider-thumb-size; // fix Ipad hitbox
            @include thumb;
          }
          &::-moz-range-thumb {
            @include thumb;
          }
          &::-ms-thumb {
            @include thumb;
          }   
        }
      }

      svg{
        z-index: 1;
        overflow: visible;
        pointer-events: none;        
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        fill: none;        
        stroke-width: 1;    
        .line{
          stroke: #F7ED7D;
        }
        .line-shadow{
          z-index: 1;
          stroke-width: 2;
          stroke: #252525;
          opacity: .35;
          display: none;
          @include device-bigger{
            display: block;
          }
        }
      }
    }
  }
}

.design-reference{
  position: fixed;
  bottom: 6px;
  right: 6px;
  color: $color-text;  
  font-size: 70%;
  display: none;
  @media(min-height: 600px){
    display: block;
  }
}
View Compiled
// String formatter
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) {
      return typeof args[number] != 'undefined' ? args[number] : match;
    });
  };
}

let app = (() => {

  const $svgLine = document.querySelector('svg .line');
  const $svgLineShadow = document.querySelector('svg .line-shadow');
  const sliderThumbSize = 20;
  const sliderHeight = 300;
  const svgViewBoxHeight = 100;
  const svgViewBoxThumbLimit = (sliderThumbSize / 2) * (svgViewBoxHeight / sliderHeight);
  const svgViewBoxGraphMax = svgViewBoxHeight - svgViewBoxThumbLimit;
  const svgViewBoxGraphMin = svgViewBoxThumbLimit;

  let ranges = {
    range1: null,
    range2: null,
    range3: null,
    range4: null,
    range5: null,
    range6: null,
    range7: null
  };
  // Only the y values changes
  let points = {
    begin: {
      x: 10,
      y: 0
    },
    point1: {
      x: 10,
      y: 0
    },
    control1: {
      x: 20,
      y: 10
    },
    control2: {
      x: 20,
      y: 0
    },
    point2: {
      x: 30,
      y: 0
    },
    control3: {
      x: 40,
      y: 0
    },
    point3: {
      x: 50,
      y: 0
    },
    control4: {
      x: 60,
      y: 0
    },
    point4: {
      x: 70,
      y: 0
    },
    control5: {
      x: 80,
      y: 0
    },
    point5: {
      x: 90,
      y: 0
    },
    control6: {
      x: 100,
      y: 0
    },
    point6: {
      x: 110,
      y: 0
    },
    control7: {
      x: 120,
      y: 0
    },
    point7: {
      x: 130,
      y: 0
    },
  };

  function mapDataRange(value) {
    // stackoverflow.com/a/929107/5707008
    // return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
    return (((value - 0) * (svgViewBoxGraphMax - svgViewBoxGraphMin)) / (svgViewBoxHeight - 0)) + svgViewBoxGraphMin;
  }

  function updateSlider($element) {
    if ($element) {

      let rangeIndex = $element.getAttribute('data-slider-index'),
        range = ranges[rangeIndex],
        value = $element.value;

      if (range === value) {
        return; // No value change, no need to update then
      }
      // Update state
      ranges[rangeIndex] = value;

      let parent = $element.parentElement,
        $thumb = parent.querySelector('.range-slider__thumb'),
        $bar = parent.querySelector('.range-slider__bar'),
        pct = value * ((sliderHeight - sliderThumbSize) / sliderHeight)

      $thumb.style.bottom = `${pct}%`;
      $bar.style.height = `calc(${pct}% + ${sliderThumbSize/2}px)`;
      //$thumb.textContent = `${value}%`;

      renderSliderGraph();
    }
  }

  function updatePoints() {
    // Convert from percentage to coordinate values    
    // Calculate and floor the values
    points.point1.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range1) / 100) | 0;
    points.point2.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range2) / 100) | 0;
    points.point3.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range3) / 100) | 0;
    points.point4.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range4) / 100) | 0;
    points.point5.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range5) / 100) | 0;
    points.point6.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range6) / 100) | 0;
    points.point7.y = svgViewBoxHeight - (svgViewBoxHeight * (ranges.range7) / 100) | 0;

    const max = svgViewBoxGraphMax;
    const min = svgViewBoxGraphMin;

    points.point1.y = mapDataRange(points.point1.y);
    points.point2.y = mapDataRange(points.point2.y);
    points.point3.y = mapDataRange(points.point3.y);
    points.point4.y = mapDataRange(points.point4.y);
    points.point5.y = mapDataRange(points.point5.y);
    points.point6.y = mapDataRange(points.point6.y);
    points.point7.y = mapDataRange(points.point7.y);

    // Update Y for the other points
    points.begin.y = points.point1.y;
    points.control1.y = points.point1.y;
    points.control2.y = points.point2.y;
    points.control3.y = points.point3.y;
    points.control4.y = points.point4.y;
    points.control5.y = points.point5.y;
    points.control6.y = points.point6.y;
    points.control7.y = points.point7.y;
  }

  function getInterpolatedLine(type) {

    let shadowOffset = 0;
    if (type === 'shadow') {
      shadowOffset = 10; // simple simulation, no fancy shadow algorithm
    }

    return 'M {0},{1} L {2},{3} C {4},{5} {6},{7} {8},{9} S {10} {11}, {12} {13} S {14} {15}, {16} {17} S {18} {19}, {20} {21} S {22} {23}, {24} {25} S {26} {27}, {28} {29}'.format(
      // M
      points.begin.x, points.begin.y,
      // L
      points.point1.x, points.point1.y,
      // C
      points.control1.x, points.control1.y,
      points.control2.x, points.control2.y + shadowOffset,
      points.point2.x, points.point2.y + shadowOffset,
      // S
      points.control3.x, points.control3.y,
      points.point3.x, points.point3.y,
      // S
      points.control4.x, points.control4.y + shadowOffset,
      points.point4.x, points.point4.y + shadowOffset,
      // S
      points.control5.x, points.control5.y,
      points.point5.x, points.point5.y,
      // S
      points.control6.x, points.control6.y + shadowOffset,
      points.point6.x, points.point6.y + shadowOffset,
      // S
      points.control7.x, points.control7.y,
      points.point7.x, points.point7.y,
    )
  }

  function reset() {
    const inputs = app.inputs;
    inputs.forEach(input => input.value = 50);
    inputs.forEach(input => app.updateSlider(input));
  }

  function renderSliderGraph() {
    updatePoints();
    $svgLine.setAttribute('d', getInterpolatedLine());
    $svgLineShadow.setAttribute('d', getInterpolatedLine('shadow'));
  }

  function selectPreset(type) {
    // Generate random graph
    const inputs = app.inputs;
    inputs.forEach(input => input.value = Math.random() * 100 | 0);
    inputs.forEach(input => app.updateSlider(input));
  }

  return {
    inputs: [].slice.call(document.querySelectorAll('.sliders input')),
    updateSlider,
    reset,
    selectPreset,
  };

})();

(function initAndSetupTheSliders() {
  const inputs = app.inputs;
  let index = 1;
  inputs.forEach(input => input.setAttribute('data-slider-index', 'range' + index++));
  inputs.forEach(input => input.value = 50);
  inputs.forEach(input => app.updateSlider(input));
  // Cross-browser support where value changes instantly as you drag the handle, therefore two event types.
  inputs.forEach(input => input.addEventListener('input', element => app.updateSlider(input)));
  inputs.forEach(input => input.addEventListener('change', element => app.updateSlider(input)));
  app.selectPreset('custom');
})();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.