Scroll the page on drag with jQuery
Asked Answered
T

9

36

I have tried using kinetic.js and the code below, however when I try this in IE11 it keeps jumping to the top every time I scroll:

$("html").kinetic();

I want to make the page scrollable on tablets and IE10 and 11 so that users can just push the page up to scroll down as you would on mobile devices.

How can I do this in pure-JS or jQuery without it jumping to the top?

Typify answered 2/11, 2013 at 14:59 Comment(0)
B
89

You can do this quite simply by recording the position of the mouse when clicked, and the current position when being dragged. Try this:

var clicked = false, clickY;
$(document).on({
    'mousemove': function(e) {
        clicked && updateScrollPos(e);
    },
    'mousedown': function(e) {
        clicked = true;
        clickY = e.pageY;
    },
    'mouseup': function() {
        clicked = false;
        $('html').css('cursor', 'auto');
    }
});

var updateScrollPos = function(e) {
    $('html').css('cursor', 'row-resize');
    $(window).scrollTop($(window).scrollTop() + (clickY - e.pageY));
}

To prevent text selection while dragging, add the following CSS:

body {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

Example fiddle


Update

Here's an version of the above as a jQuery plugin, extended to allow both vertical and horizontal scrolling via the settings. It also allows you to change the cursor that's used too.

(function($) {
  $.dragScroll = function(options) {
    var settings = $.extend({
      scrollVertical: true,
      scrollHorizontal: true,
      cursor: null
    }, options);

    var clicked = false,
      clickY, clickX;

    var getCursor = function() {
      if (settings.cursor) return settings.cursor;
      if (settings.scrollVertical && settings.scrollHorizontal) return 'move';
      if (settings.scrollVertical) return 'row-resize';
      if (settings.scrollHorizontal) return 'col-resize';
    }

    var updateScrollPos = function(e, el) {
      $('html').css('cursor', getCursor());
      var $el = $(el);
      settings.scrollVertical && $el.scrollTop($el.scrollTop() + (clickY - e.pageY));
      settings.scrollHorizontal && $el.scrollLeft($el.scrollLeft() + (clickX - e.pageX));
    }

    $(document).on({
      'mousemove': function(e) {
        clicked && updateScrollPos(e, this);
      },
      'mousedown': function(e) {
        clicked = true;
        clickY = e.pageY;
        clickX = e.pageX;
      },
      'mouseup': function() {
        clicked = false;
        $('html').css('cursor', 'auto');
      }
    });
  }
}(jQuery))

$.dragScroll();
/* Note: CSS is not relevant to the solution. 
   This is only needed for this demonstration */

body,
html {
  padding: 0;
  margin: 0;
}

div {
  height: 1000px;
  width: 2000px;
  border-bottom: 3px dashed #EEE;
  /* gradient is only to make the scroll movement more obvious */
  background: rgba(201, 2, 2, 1);
  background: -moz-linear-gradient(-125deg, rgba(201, 2, 2, 1) 0%, rgba(204, 0, 204, 1) 16%, rgba(94, 0, 201, 1) 31%, rgba(0, 153, 199, 1) 43%, rgba(0, 199, 119, 1) 56%, rgba(136, 199, 0, 1) 69%, rgba(199, 133, 0, 1) 83%, rgba(107, 0, 0, 1) 100%);
  background: -webkit-gradient(left top, right bottom, color-stop(0%, rgba(201, 2, 2, 1)), color-stop(16%, rgba(204, 0, 204, 1)), color-stop(31%, rgba(94, 0, 201, 1)), color-stop(43%, rgba(0, 153, 199, 1)), color-stop(56%, rgba(0, 199, 119, 1)), color-stop(69%, rgba(136, 199, 0, 1)), color-stop(83%, rgba(199, 133, 0, 1)), color-stop(100%, rgba(107, 0, 0, 1)));
  background: -webkit-linear-gradient(-125deg, rgba(201, 2, 2, 1) 0%, rgba(204, 0, 204, 1) 16%, rgba(94, 0, 201, 1) 31%, rgba(0, 153, 199, 1) 43%, rgba(0, 199, 119, 1) 56%, rgba(136, 199, 0, 1) 69%, rgba(199, 133, 0, 1) 83%, rgba(107, 0, 0, 1) 100%);
  background: -o-linear-gradient(-125deg, rgba(201, 2, 2, 1) 0%, rgba(204, 0, 204, 1) 16%, rgba(94, 0, 201, 1) 31%, rgba(0, 153, 199, 1) 43%, rgba(0, 199, 119, 1) 56%, rgba(136, 199, 0, 1) 69%, rgba(199, 133, 0, 1) 83%, rgba(107, 0, 0, 1) 100%);
  background: -ms-linear-gradient(-125deg, rgba(201, 2, 2, 1) 0%, rgba(204, 0, 204, 1) 16%, rgba(94, 0, 201, 1) 31%, rgba(0, 153, 199, 1) 43%, rgba(0, 199, 119, 1) 56%, rgba(136, 199, 0, 1) 69%, rgba(199, 133, 0, 1) 83%, rgba(107, 0, 0, 1) 100%);
  background: linear-gradient(-110deg, rgba(201, 2, 2, 1) 0%, rgba(204, 0, 204, 1) 16%, rgba(94, 0, 201, 1) 31%, rgba(0, 153, 199, 1) 43%, rgba(0, 199, 119, 1) 56%, rgba(136, 199, 0, 1) 69%, rgba(199, 133, 0, 1) 83%, rgba(107, 0, 0, 1) 100%);
  filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#c90202', endColorstr='#6b0000', GradientType=1);
  color: #EEE;
  padding: 20px;
  font-size: 2em;
}

body {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div>First...</div>

<div>Second...</div>
Billowy answered 2/11, 2013 at 15:15 Comment(6)
How can I prevent the selection of items on the page when i am scrolling?Typify
I think that should be -moz-none;Typify
-moz-none is non standard so I omitted it.Billowy
@Typify don't do it that way - appending lots of css properties to every element in the DOM will be unbelievably slow. Use this instead: jsfiddle.net/RoryMcCrossan/883RP/1Billowy
I'd recommend to add e.preventDefault(); below 'mousedown': function(e) { to avoid the auto-scroll when the mouse goes outside the window (see also: #12939016)Reluctance
Well done! Please note, you might need some additional check and handler for case the cursor is outside of the viewport, to avoid jumping back to top, etc.Elyse
M
10

I just like to add. Using Rory's code I made horizontal scrolling.

var clicked = false, base = 0;

$('#someDiv').on({
    mousemove: function(e) {
        clicked && function(xAxis) {
            var _this = $(this);
            if(base > xAxis) {
                base = xAxis;
                _this.css('margin-left', '-=1px');
            }
            if(base < xAxis) {
                base = xAxis;
                _this.css('margin-left', '+=1px');
            }
        }.call($(this), e.pageX);
    },
    mousedown: function(e) {
        clicked = true;
        base = e.pageX;
    },
    mouseup: function(e) {
        clicked = false;
        base = 0;
    }
});
Monamonachal answered 13/3, 2014 at 9:46 Comment(0)
A
9

This code will work on horizontal and vertical mouse drag scroll. It's pretty simple.

var curYPos = 0,
    curXPos = 0,
    curDown = false;

window.addEventListener('mousemove', function(e){ 
  if(curDown === true){
    window.scrollTo(document.body.scrollLeft + (curXPos - e.pageX), document.body.scrollTop + (curYPos - e.pageY));
  }
});

window.addEventListener('mousedown', function(e){ curDown = true; curYPos = e.pageY; curXPos = e.pageX; });
window.addEventListener('mouseup', function(e){ curDown = false; }); 
Aposematic answered 1/12, 2015 at 21:27 Comment(0)
F
3

Based on the first answer, this is the code for horizontal scroll on mouse drag:

var clicked = false, clickX;
$(document).on({
    'mousemove': function(e) {
        clicked && updateScrollPos(e);
    },
    'mousedown': function(e) {
        e.preventDefault();        
        clicked = true;
        clickX = e.pageX;
    },
    'mouseup': function() {
        clicked = false;
        $('html').css('cursor', 'auto');
    }
});

var updateScrollPos = function(e) {
    $('html').css('cursor', 'grabbing');
    $(window).scrollLeft($(window).scrollLeft() + (clickX - e.pageX));
}
Fructuous answered 27/11, 2016 at 18:18 Comment(0)
L
3

Base on Rory McCrossan's idea, implemented with AngularJS2.

import {Directive, ElementRef, OnDestroy, Input} from "@angular/core";

declare var jQuery: any;

@Directive({
    selector: '[appDragScroll]'
})
export class DragScrollDirective implements OnDestroy {

    @Input() scrollVertical: boolean = true;
    @Input() scrollHorizontal: boolean = true;

    private dragging = false;
    private originalMousePositionX: number;
    private originalMousePositionY: number;
    private originalScrollLeft: number;
    private originalScrollTop: number;

    constructor(private nodeRef: ElementRef) {
        let self = this;

        jQuery(document).on({
            "mousemove": function (e) {
                self.dragging && self.updateScrollPos(e);
            },
            "mousedown": function (e) {
                self.originalMousePositionX = e.pageX;
                self.originalMousePositionY = e.pageY;
                self.originalScrollLeft = jQuery(self.nodeRef.nativeElement).scrollLeft();
                self.originalScrollTop = jQuery(self.nodeRef.nativeElement).scrollTop();
                self.dragging = true;
            },
            "mouseup": function (e) {
                jQuery('html').css('cursor', 'auto');
                self.dragging = false;
            }
        });

    }

    ngOnDestroy(): void {
        jQuery(document).off("mousemove");
        jQuery(document).off("mousedown");
        jQuery(document).off("mouseup");
    }

    private updateScrollPos(e) {
        jQuery('html').css('cursor', this.getCursor());

        let $el = jQuery(this.nodeRef.nativeElement);
        if (this.scrollHorizontal) {
            $el.scrollLeft(this.originalScrollLeft + (this.originalMousePositionX - e.pageX));
        }
        if (this.scrollVertical) {
            $el.scrollTop(this.originalScrollTop + (this.originalMousePositionY - e.pageY));
        }
    }

    private getCursor() {
        if (this.scrollVertical && this.scrollHorizontal) return 'move';
        if (this.scrollVertical) return 'row-resize';
        if (this.scrollHorizontal) return 'col-resize';
    }

}
Laskowski answered 11/2, 2017 at 8:40 Comment(0)
O
0

I modified Rory's code quite a bit, and I got per-element side scrolling to work. I needed that for a project that had multiple scrollable tiles in a single view for a webapp. Add the .drag class to any element, possibly do a bit of styling, and it should be good to go.

// jQuery sidescroll code. Can easily be modified for vertical scrolling as well.
// This code was hacked together so clean it up if you use it in prod.
// Written by Josh Moore
// Thanks to Rory McCrossan for a good starting point

// How far away the mouse should move on a drag before interrupting click
// events (your own code must also interrupt regular click events with a
// method that calls getAllowClick())
const THRESHOLD = 32;
var clicked = false;
var allowClick = true;

// mouseX: most recent mouse position. updates when mouse moves.
//     el: jQuery element that will be scrolled.
//    thX: "threshold X", where the mouse was at the beginning of the drag
var mouseX, startY, el, thX;

// Add the .drag class to any element that should be scrollable.
// You may need to also add these CSS rules:
//   overflow: hidden; /* can be replaced with overflow-x or overflow-y */
//   whitespace: none;
function drag() {
    $('.drag').on({
        'mousemove': e => {
            if (el != null && clicked) {
                el.scrollLeft(el.scrollLeft() + (mouseX - e.pageX));
                mouseX = e.pageX;
                allowClick = Math.abs(thX - mouseX) > THRESHOLD ? false : true;
            }
        },
        'mousedown': e => {
            clicked = true;
            // This lets the user click child elements of the scrollable.
            // Without it, you must click the .drag element directly to be able to scroll.
            el = $(e.target).closest('.drag');
            mouseX = e.pageX;
            thX = e.pageX;
        },
        'mouseup': e => {
            clicked = false;
            el = null;
            allowClick = true;
        }
    });
}

function getAllowClick() {
    return allowClick;
}

Again, I had no use for vertical scrolling but it would be pretty simple to add (replace X's with Y's, scrollTop() instead of scrollLeft(), etc). Hope this helps someone in the future!

Osrock answered 16/4, 2019 at 19:45 Comment(0)
M
0

This framework is written in vanilla javascript, and worked best for me.

It also supports scroller inside divs.

Note: If you create dynamic content, call dragscroll.reset(); after render.

dragscroll

Usage

demo

Metagalaxy answered 7/11, 2020 at 6:20 Comment(0)
C
0

Here follows a simple scroll solution based on a class for individual elements

      var cursordown = false;
      var cursorypos = 0;
      var cursorxpos = 0;
      $('.cursorgrab').mousedown(function(e){
        cursordown = true; 
        cursorxpos = $(this).scrollLeft() + e.clientX; 
        cursorypos = $(this).scrollTop() +e.clientY;
      }).mousemove(function(e){
        if(!cursordown) return;
        try { $(this).scrollLeft(cursorxpos - e.clientX); } catch(e) { }
        try { $(this).scrollTop(cursorypos - e.clientY); } catch(e) { }
      }).mouseup(end = function(e){
        cursordown = false;
      }).mouseleave(end)
        .css('user-select', 'none')
        .css('cursor', 'grab');

Full validated for both horizontal and vertical scrolling

You just have to set .cursorgrab class into your element

Cathicathie answered 12/7, 2022 at 20:49 Comment(0)
B
0

Vanilla JS:

var clicked = false, clickY, clickX;
document.onmousemove = (e) => {
  clicked && updateScrollPos(e);
}
document.onmousedown = (e) => {
  clicked = true;
  clickY = e.pageY;
  clickX = e.pageX;
  document.querySelector("body").style.userSelect = "none";
}
document.onmouseup = (e) => {
  clicked = false;
  document.querySelector('html').style.cursor='grab';
}
var updateScrollPos = function(e) {
  document.querySelector('html').style.cursor='grabbing';
  window.scrollTo(window.scrollX + (clickX - e.pageX), window.scrollY + (clickY - e.pageY));
}
Blameful answered 4/3, 2023 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.