Search code examples
htmlvue.jsinputslideruser-experience

Fine-tuning values in HTML input range using modifier keys


I'm currently working on an HTML interface that involves using an input range element. In electronic music production, it's common to have the ability to fine-tune control values using special keys like Alt or Shift. From my understanding, it seems unlikely that pure HTML input range supports this feature by default. However, before delving into finding a solution using a framework like Vue.js, I would like to confirm if achieving this functionality is possible in pure HTML.

Additionally, I would prefer to avoid suboptimal solutions that involve using the HTML canvas element. While I'm aware it may be a potential solution, I would like to explore other possibilities first.

Can anyone provide guidance, suggestions, or insights into achieving fine-tuning functionality in pure HTML input range? I would appreciate knowing if it's possible before embarking on a Vue.js-specific solution. Thank you!


Solution

  • You can process the mouse events yourself:

    const prop = Object.getOwnPropertyDescriptor(Object.getPrototypeOf($range), 'value');
    const _set = prop.set;
    prop.set = function(val){
      if(val < $range.min){
        val = $range.min;
      }else if(val > $range.max){
        val = $range.max;
      }
      $value.textContent = val;
      _set.call($range, val);
    };
    Object.defineProperty($range, 'value', prop);
    
    let control = false;
    document.addEventListener('keydown', e => {
      e.key = 'Control' && !e.repeat && start && (control = true) && setStart(event);
    });
    
    document.addEventListener('keyup', e => {
      e.key = 'Control' && (control = false);
    });
    
    $range.value = 0;
    
    const width = $range.offsetWidth;
    
    let start, startVal;
    let event;
    
    const setStart = e => {
      start = e.pageX;
      const rect = $range.getBoundingClientRect();
      const val = (start - rect.left) / rect.width * 1000;
      $range.value = startVal = val;
    }
    
    
    $container.addEventListener('mousedown', setStart);
    
    document.addEventListener('mousemove', e => {
      
      event = e;
      
      if(!start){
        return;
      }
      
      let delta = e.pageX - start;
      if(control){
        delta = delta/10;
      }
      
      
      const rect = $range.getBoundingClientRect();
      delta = delta / rect.width * 1000;
      
      $range.value = startVal + delta;
      
    });
    
    document.addEventListener('mouseup', e => {
    
      start = null;
    
    });
    input{
      pointer-events: none;
    }
    <p>Audio settings:</p>
    
    <div>
      <div id="$container" style="display:inline-block">
      <input id="$range" type="range" id="volume" name="volume"
             min="0" max="1000">
             </div>
      <label for="volume">Volume</label>
      <div id="$value"></div>
    </div>