Search code examples
javascriptscrolldragsmoothing

how to add smoothness to the drag and scroll


I have a horizontal scroll area that is scrollable also by drag. I would like to add smoothness to this drag and scroll, I mean I want it to keep move on the x axis after I leave the drag and stop slowly.

here's a small snippet of the whole scene as it works right now without the 'smoothness'

const slider = document.querySelector(".wrapper");
      const preventClick = (e) => {
        e.preventDefault();
        e.stopImmediatePropagation();
      }
      let isDown = false;
      var isDragged = false;
      let startX;
      let scrollLeft;

      slider.addEventListener("mousedown", e => {
        isDown = true;
        slider.classList.add("active");
        startX = e.pageX - slider.offsetLeft;
        scrollLeft = slider.scrollLeft;
      });

      slider.addEventListener("mouseleave", () => {
        isDown = false;
        slider.classList.remove("active");
      });

      slider.addEventListener("mouseup", e => {
        isDown = false;
        const elements = document.getElementsByClassName("book");
        if(isDragged){
            for(let i = 0; i<elements.length; i++){
                  elements[i].addEventListener("click", preventClick);
            }
        }else{
            for(let i = 0; i<elements.length; i++){
                  elements[i].removeEventListener("click", preventClick);
            }
        }
        slider.classList.remove("active");
        isDragged = false;
      });

      slider.addEventListener("mousemove", e => {
        if (!isDown) return;
        isDragged =  true;
        e.preventDefault();
        const x = e.pageX - slider.offsetLeft;
        const walk = (x - startX) * 2;
        slider.scrollLeft = scrollLeft - walk;
      });

      document.getElementsByClassName("book").ondragstart = function() {
        console.log("Drag start");
      };
.wrapper {
        position: relative;
        display: -webkit-box;
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        overflow: auto;
        min-width: 100%;
      }

      .book {
        width: auto;
        height: 100vh;
        min-width: 50vw;
      }

      .one {
        background-color: #d07fe0;
      }

      .two {
        background-color: #808888;
      }

      .three {
        background-color: #83e7dc;
      }

      .four {
        background-color: #edf7a8;
      }

      .five {
        background-color: #e9d98f;
      }

      .six {
        background-color: #fdd;
      }
<body>
      <div class="wrapper">
        <a href="https://stackoverflow.com/" class="book one"></a>
        <a href="https://stackoverflow.com/" class="book two"></a>
        <a href="https://stackoverflow.com/" class="book three"></a>
        <a href="https://stackoverflow.com/" class="book four"></a>
        <a href="https://stackoverflow.com/" class="book five"></a>
        <a href="https://stackoverflow.com/" class="book six"></a>
      </div>
    </body>

I would appreciate any help thanks, nir


Solution

  • You can add a velocity variable that tells how many pixels it is scrolled each frame:

    let velocity = 0;
    slider.addEventListener("mousemove", e => {
            if (!isDown) return;
            isDragged =  true;
            e.preventDefault();
            let lastX = slider.scrollLeft;
            const x = e.pageX - slider.offsetLeft;
            const walk = (x - startX) * 2;
            slider.scrollLeft = scrollLeft - walk;
    
            velocity = lastX - slider.scrollLeft;
          });
    

    Then, you can add a function to keep moving the content after the use stops clicking:

    function smooth() {
                  console.log(velocity);
              if (Math.abs(velocity) > 0) {
                  if (Math.abs(velocity) < 2) {
                      velocity = 0;
                  }
                  if (velocity > 0) {
                    velocity  -= 2;
                  }
                  else {
                    velocity += 2;
                  }
                  slider.scrollLeft -= velocity;
                  requestAnimationFrame(smooth);
              }
          }
    

    After that, call the function onmouseup:

        slider.addEventListener("mousemove", e => {
            if (!isDown) return;
            isDragged =  true;
            e.preventDefault();
            let lastX = slider.scrollLeft;
            const x = e.pageX - slider.offsetLeft;
            const walk = (x - startX) * 2;
            slider.scrollLeft = scrollLeft - walk;
    
            velocity = lastX - slider.scrollLeft;
          });
    

    The full code is below

    const slider = document.querySelector(".wrapper");
              const preventClick = (e) => {
                e.preventDefault();
                e.stopImmediatePropagation();
              }
              let isDown = false;
              var isDragged = false;
              let startX;
              let scrollLeft;
    		  let velocity = 0;
    
              slider.addEventListener("mousedown", e => {
                velocity = 0; // Cancel previous velocity
                isDown = true;
                slider.classList.add("active");
                startX = e.pageX - slider.offsetLeft;
                scrollLeft = slider.scrollLeft;
              });
    
              slider.addEventListener("mouseleave", () => {
                isDown = false;
                slider.classList.remove("active");
              });
    
              slider.addEventListener("mouseup", e => {
                isDown = false;
                const elements = document.getElementsByClassName("book");
                if(isDragged){
                    for(let i = 0; i<elements.length; i++){
                          elements[i].addEventListener("click", preventClick);
                    }
                }else{
                    for(let i = 0; i<elements.length; i++){
                          elements[i].removeEventListener("click", preventClick);
                    }
                }
                slider.classList.remove("active");
                isDragged = false;
    			requestAnimationFrame(smooth);
              });
    
              slider.addEventListener("mousemove", e => {
                if (!isDown) return;
                isDragged =  true;
                e.preventDefault();
    			let lastX = slider.scrollLeft;
                const x = e.pageX - slider.offsetLeft;
                const walk = (x - startX) * 2;
                slider.scrollLeft = scrollLeft - walk;
    
    			velocity = lastX - slider.scrollLeft;
              });
    
              document.getElementsByClassName("book").ondragstart = function() {
                console.log("Drag start");
              };
    
    		  function smooth() {
    			  if (Math.abs(velocity) > 0) {
            // Change the 2s here to change how quickly the scrolling stops
    				  if (Math.abs(velocity) < 2) {
    					  velocity = 0;
    				  }
    				  if (velocity > 0) {
    					velocity  -= 2;
    				  }
    				  else {
    					velocity += 2;
    				  }
            		  slider.scrollLeft -= velocity;
    				  requestAnimationFrame(smooth);
    			  }
    		  }
        .wrapper {
        	position: relative;
        	display: -webkit-box;
        	display: -webkit-flex;
        	display: -ms-flexbox;
        	display: flex;
        	overflow: auto;
        	min-width: 100%;
        }
    
        .book {
          	width: auto;
          	height: 100vh;
          	min-width: 50vw;
        }
    
        .one {
          	background-color: #d07fe0;
        }
    
        .two {
         	 background-color: #808888;
        }
    
        .three {
         	 background-color: #83e7dc;
        }
    
        .four {
         	 background-color: #edf7a8;
        }
    
        .five {
         	 background-color: #e9d98f;
        }
    
        .six {
         	 background-color: #fdd;
        }
    <div class="wrapper">
            <a href="https://stackoverflow.com/" class="book one"></a>
            <a href="https://stackoverflow.com/" class="book two"></a>
            <a href="https://stackoverflow.com/" class="book three"></a>
            <a href="https://stackoverflow.com/" class="book four"></a>
            <a href="https://stackoverflow.com/" class="book five"></a>
            <a href="https://stackoverflow.com/" class="book six"></a>
          </div>