7

I added the following code in order to scroll with my mouse (scroll on click+drag, not by the mousewheel). So far, so good - works like a charm:

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));
}

I am trying to change this scroll behavior so that each directional click+drag mouse movement jumps to the next/closest hash after e.g. a 10px drag. In other words, a mouse scroll up should jump to the next hash above the current position, scrolling down should jump to the next one below.

This doesn't seem to be covered by any of the related questions.

Edit:

I think I need to replace

$(window).scrollTop($(window).scrollTop() + (clickY - e.pageY));

by parts of the solution in the link that follows. Unfortunately, this seems to be above my skill level:

how to get nearest anchor from the current mouse position on mouse move

Solution:

I used Saeed Ataee's answer, really happy about that code, but replaced the mouse-wheel code portion with the following one I had in place already, just happened to work better on my end (I am sure his is fine, just giving an alternative here):

$('#nav').onePageNav();


var $current, flag = false;

$('body').mousewheel(function(event, delta) {
if (flag) { return false; }
$current = $('div.current');

if (delta > 0) {
    $prev = $current.prev();

    if ($prev.length) {
        flag = true;
        $('body').scrollTo($prev, 1000, {
            onAfter : function(){
                flag = false;
            }
        });
        $current.removeClass('current');
        $prev.addClass('current');
    }
} else {
    $next = $current.next();

    if ($next.length) {
        flag = true;
        $('body').scrollTo($next, 1000, {
            onAfter : function(){
                flag = false;
            }
        });
        $current.removeClass('current');
        $next.addClass('current');
    }
}

event.preventDefault();

});

2
  • Possible duplicate of How to scroll HTML page to given anchor? Commented May 11, 2018 at 18:08
  • 1
    Thanks for the input, but it isn't a duplicate in my opinion at all, even though it might contain helpful code. What I am trying to do here is rewrite my mouse click scroll behavior to scroll to a hash rather than just scroll up and down.
    – user8139445
    Commented May 11, 2018 at 18:58

3 Answers 3

4

I hope this helps you

let currentElement = 0,
  maxLength = $("div[id^='section']").length;

$(document).ready(function() {
  $(document).on("mousewheel", function(e) {
    if (e.originalEvent.wheelDelta > 0) {
      currentElement = (currentElement > 0) ? currentElement - 1 : 0;

      $("html, body").animate({
          scrollTop: $("#section-" + currentElement).offset().top
        },
        200
      );
    } else {
      currentElement = (currentElement < maxLength - 1) ? currentElement + 1 : currentElement;
      $("html, body").animate({
          scrollTop: $("#section-" + currentElement).offset().top
        },
        200
      );
    }
  });
});
<html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
  <div id="section-0">Section 1</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-1">Section 2</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-2">Section 3</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-3">Section 4</div>
</body>

</html>

2
  • Great code, but not what I was looking for. I got this functionality plus the one I posted in my question alerady in place. What I am trying to do is to modify the code I included in my question to jump to the next hash on click+drag, rather than just scroll a few pixels up.
    – user8139445
    Commented May 15, 2018 at 16:43
  • The Drag and Move is indeed what I need, thanks! Ideally I would like to add that last bit into my existing code rather than replacing all by this fullPage code.
    – user8139445
    Commented May 15, 2018 at 18:41
2

I see a few things you have to solve:

1) Mark and find which elements you want to scroll to

Put something like an ID attribute or class on the elements you'd like to scroll to. With the ID attribute you can use the old fashioned hash URL navigation (http://some-url/#some-anchor) in addition to your new method to navigate.

Let's say you use the format #scroll-to-A, #scroll-to-B, etc. as IDs for your anchors. You can then target all elements with an ID containing the string scroll-to using document.querySelectorAll with this:

const scrollElements = document.querySelectorAll("[id*='scroll-to']");.

querySelectorAll does not return an Element array, but a NodeList. You can convert it into an array using Array.from(scrollElements). It can be useful to know for the rest of the code I'm presenting.

2) Given any vertical scroll position on the page, find which element should be scrolled into view next

Using Element.getBoundingClientRect() you can determine how many pixels from the top of the screen an element is positioned:

function getTopPositionForElement(el) {
    return el.getBoundingClientRect().top + window.scrollY;
}

Then you can calculate which element to scroll to next by checking which is closest to the top of the page. Use Array.sort to determine which is closest in the array and then choose the first element in the list:

function calculateNextElement(direction) {
    // Handle the cases for direction here...

    switch (direction) {
        case "down": 
            return scrollElements.sort((a, b) => { 
                return getTopPositionForElement(a) > getTopPositionForElement(b) ? -1 : 1;
            })[0];
        case "up":
            // Fill in here
        default: 
            // Fill in here
    }
}

One thing you have to add here is handling the two cases of which direction the user is scrolling. This implementation only takes into account the downward scrolling. Maybe this can help.

3) Scroll element into view

Using Element.scrollIntoView you can scroll the element into view so that it is visible to the user. I chose to add "smooth" scrolling below, but you can use other options. Check the documentation.

function scrollElementIntoView(direction) {
    const next = calculateNextElement(direction);
    next.scrollIntoView({
        behaviour: "smooth"
    });
}

4) Capture vertical swipe mouse movements

This one seems a bit tricky. You could use something like HammerJS if you are comfortable using a library.

Essentially what it comes down to is capturing the mouse on mousedown and then again on mouseup, checking how many pixels the mouse has moved between the two events. I would also suggest checking the movement speed between the two points.

Here is an attempt:

let previousEvent;
let time;

// Keep track of the position where the user starts dragging
document.addEventListener("mousedown", function(event) {
    previousEvent = event;
    time = new Date();
});

// When the mouse is released, figure out which direction the mouse went
document.addEventListener("mouseup", function(event) {
    const pixelsY = previousEvent.screenY - event.screenY;
    const timeTaken = Date.now() - time.getTime();
    const speed = pixelsY / timeTaken;

    let direction;
    if (speed > 1) {
        direction = "up";
    } else if (speed < -1) {
        direction = "down";
    } else {
        direction = "tap";
    }

    scrollElementIntoView(direction);
});

Good luck!

9
  • 1
    The snipped of the questions allows to click and drag in any direction (a few px up or down). I am trying to get an automatic jump to the nearest hash/section to the bottom when the listener notices a click and a scrolling down behavior and an automatic jump to the nearest hash/section to the top when there is a click with an upwards scroll movement. Any idea how to rewrite your code to achieve that? Or does your code do just that I am failing to implement it correctly?
    – user8139445
    Commented May 14, 2018 at 19:09
  • 1
    The code I've written down does not do that. However, I think I've given you most of the pieces to the puzzles. You should be able to figure it out :)
    – maxpaj
    Commented May 14, 2018 at 19:22
  • Your code by itself doesn't take any drag behavior into account, right? It's also written in JS while the snipped above is JQuery. i am still pretty beginner and even though I understand what your code does, I am not able to rewrite the original snipped to JS.
    – user8139445
    Commented May 14, 2018 at 21:17
  • Oh okay. You mean something similar to how you flick the screen on your mobile device when you want to scroll? For that you'd need to extend the functionality of your mouse listener.
    – maxpaj
    Commented May 15, 2018 at 6:06
  • Exactly. I got already code in place (before the answer above was posted) that jumps to the next hash on mousewheel trigger. On top of that, I added the code I posted above that allows to click and drag with the mouse. I want to click>hold>drag in a direction>"jump to next hash after 10px of drag". I thought it was possible to modify the mousemove behavior in order to account for this.
    – user8139445
    Commented May 15, 2018 at 16:37
1
+50

I think this is your answer.

let currentElement = 0,
  maxLength = $("div[id^='section']").length,
  changeSw = false;

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

  var updateScrollPos = function(e) {
    $("html").css("cursor", "row-resize");
    // $(window).scrollTop($(window).scrollTop() + (clickY - e.pageY));
    if (changeSw && clickY - e.pageY > 0) {
      currentElement =
        (currentElement < maxLength - 1) ? currentElement + 1 : currentElement;
      changeSw = false;
      clicked = false;
    } else if (changeSw && clickY - e.pageY <= 0) {
      currentElement = currentElement > 0 ? currentElement - 1 : 0;
      changeSw = false;
      clicked = false;
    }
    console.log(currentElement)
    $("html, body").animate({
        scrollTop: $("#section-" + currentElement).offset().top
      },
      200
    );

  };

  $(document).on("mousewheel", function(e) {
    if (e.originalEvent.wheelDelta > 0) {
      currentElement = currentElement > 0 ? currentElement - 1 : 0;

      $("html, body").animate({
          scrollTop: $("#section-" + currentElement).offset().top
        },
        200
      );
    } else {
      currentElement =
        currentElement < maxLength - 1 ? currentElement + 1 : currentElement;
      $("html, body").animate({
          scrollTop: $("#section-" + currentElement).offset().top
        },
        200
      );
    }
  });
});
<html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
  <div id="section-0">Section 1</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-1">Section 2</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-2">Section 3</div>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <div id="section-3">Section 4</div>
</body>

</html>

4
  • Thanks so much. The mouse-wheel code I had in place worked better for me, but the rest is exactly what I was looking for. Thanks so much! I added the mouse-wheel code to my question in case you wanna check it out.
    – user8139445
    Commented May 17, 2018 at 17:36
  • How would I add a condition that requires a minimum drag of, let's say, 100px in either direction before the jump gets triggered?
    – user8139445
    Commented May 17, 2018 at 17:49
  • I think changing clickY - e.pageY > 0 to clickY - e.pageY > 100 solve that. Check it. Good luck.
    – Saeed
    Commented May 17, 2018 at 19:17
  • That's not it, but I'll try to figure it out. Thanks!
    – user8139445
    Commented May 17, 2018 at 22:59