Trying to get this smoother and more natural in behavior

83 views Asked by At

My implementation, http://kodhus.com/kodnest/land/PpNFTgp

I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.

JavaScript:

const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;

document.addEventListener('mousedown', function(e) {
  if (e.target.classList.contains('thumb-indicator')) {
    dragging = true;
    thumbIndicator.classList.add('focus');  
  }
});

document.addEventListener('mousemove', function(e) {

  if (dragging) {
      console.log('moving', e)
    if (e.clientX < sliderContainerStart) {
      translate = 0;
    } else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
      translate = sliderContainerWidth;
    } else {
      translate =  e.clientX - sliderContainer.offsetLeft;  
    }
    
    thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
    trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth +  ')'
  }
});

function setPercentage() {
   thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
  trackProgress.style.transform = 'scaleX(' + percentage/100 +  ')';
}

function init() {
 setPercentage(); 
}

init();

document.addEventListener('mouseup', function(e) {
  dragging = false;
  thumbIndicator.classList.remove('focus');
});

EDIT: Is there a way to smoothly and naturally increment by one for every slow move?

Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?

1

There are 1 answers

4
Gershom Maes On BEST ANSWER

The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:

  • stopPropagation when clicking the .thumb element
  • drag stops if window loses focus
  • pointer-events: none; applied to every part of the slider but the .thumb element

let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {

  // Now `thumb`, `bar` and `slider` are the elements that concern us
  let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
  
  let changed = amt => {
    thumb.style.left = `${amt * 100}%`;
    bar.style.width = `${amt * 100}%`;
    valueChangeCallback(amt);
  };
  
  // Pressing down on `thumb` activates dragging
  thumb.addEventListener('mousedown', evt => {
    thumb.classList.add('active');
    evt.preventDefault();
    evt.stopPropagation();
  });
  
  // Releasing the mouse button (anywhere) deactivates dragging
  document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
  
  // If the window loses focus dragging also stops - this can be a very
  // nice quality of life improvement!
  window.addEventListener('blur', evt => thumb.classList.remove('active'));
  
  // Now we have to act when the mouse moves...
  document.addEventListener('mousemove', evt => {
    
    // If the drag isn't active do nothing!
    if (!thumb.classList.contains('active')) return;
    
    // Compute `xRelSlider`, which is the mouse position relative to the
    // left side of the slider bar. Note that *client*X is compatible with
    // getBounding*Client*Rect, and using these two values we can quickly
    // get the relative x position.
    let { width, left } = slider.getBoundingClientRect();
    
    // Consider mouse x, subtract left offset of slider, and subtract half
    // the width of the thumb (so drags position the center of the thumb,
    // not its left side):
    let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
    
    // Clamp `xRelSlider` between 0 and the slider's width
    if (xRelSlider < 0) xRelSlider = 0;
    if (xRelSlider > width) xRelSlider = width;
    
    // Apply styling (using percents is more robust!)
    changed(xRelSlider / width);
    
    evt.preventDefault();
    evt.stopPropagation();
    
  });
  
  slider.addEventListener('mousedown', evt => {
    
    let { width, left } = slider.getBoundingClientRect();
    
    // Clicking the slider also activates a drag
    thumb.classList.add('active');
    
    // Consider mouse x, subtract left offset of slider, and subtract half
    // the width of the thumb (so drags position the center of the thumb,
    // not its left side):
    let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
    
    // Apply styling (using percents is more robust!)
    changed(xRelSlider / width);
    
    evt.preventDefault();
    evt.stopPropagation();
    
  });
  
  changed(0);
  
};

let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
  position: absolute;
  width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
  left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
  position: absolute;
  left: 0; top: 0; width: 0; height: 100%;
  background-color: #000;
  pointer-events: none;
}
.slider > .thumb {
  position: absolute;
  width: 20px; height: 20px; background-color: #000; border-radius: 100%;
  left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
  box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
  <div class="bar"></div>
  <div class="thumb"></div>
</div>
<div class="value"></div>