Reputation:
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();
});
Upvotes: 7
Views: 1906
Reputation: 6791
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 usingArray.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!
Upvotes: 2
Reputation: 5488
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>
Upvotes: 1
Reputation: 5488
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>
Upvotes: 4