Can one use Window.Onscroll method to include detection of scroll direction?
If you record the scrollX and scrollY on page load and each time a scroll event occurs, then you can compare the previous values with the new values to know which direction you scrolled. Here's a proof of concept:
function scrollFunc(e) {
if ( typeof scrollFunc.x == 'undefined' ) {
scrollFunc.x=window.pageXOffset;
scrollFunc.y=window.pageYOffset;
}
var diffX=scrollFunc.x-window.pageXOffset;
var diffY=scrollFunc.y-window.pageYOffset;
if( diffX<0 ) {
// Scroll right
} else if( diffX>0 ) {
// Scroll left
} else if( diffY<0 ) {
// Scroll down
} else if( diffY>0 ) {
// Scroll up
} else {
// First scroll event
}
scrollFunc.x=window.pageXOffset;
scrollFunc.y=window.pageYOffset;
}
window.onscroll=scrollFunc
With jquery, you can also register a custom scroll event which supplies the scroll change as an argument to the event handler:
var previous_scroll = $(window).scrollTop();
$(window).on('scroll', function() {
var scroll = $(window).scrollTop(),
scroll_change = scroll - previous_scroll;
previous_scroll = scroll;
$(window).trigger('custom_scroll', [scroll_change]);
});
Then instead of scroll, bind to custom_scroll:
$(window).on('custom_scroll', function pos(e, scroll_change) {
console.log(scroll_change);
});
I had trouble making this work in ie8 (although it is compliant for ie9, FF and Chrome) - all scrolls seem to be detected as horizontal.
Here is a modified script demo that also works in ie8 and may cover a few more browsers.
function scrollFunc(e) {
function getMethod() {
var x = 0, y = 0;
if ( typeof( window.pageYOffset ) == 'number' ) {
x = window.pageXOffset;
y = window.pageYOffset;
}
else if( document.body && (document.body.scrollLeft || document.body.scrollTop ) ) {
x = document.body.scrollLeft;
y = document.body.scrollTop;
}
else if( document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
x = document.documentElement.scrollLeft;
y = document.documentElement.scrollTop;
}
return [x, y];
}
var xy = getMethod();
var xMethod = xy[0];
var yMethod = xy[1];
if ( typeof scrollFunc.x == 'undefined' ) {
scrollFunc.x = xMethod;
scrollFunc.y = yMethod;
}
var diffX = scrollFunc.x - xMethod;
var diffY = scrollFunc.y - yMethod;
if( diffX<0 ) {
// Scroll right
} else if( diffX>0 ) {
// Scroll left
} else if( diffY<0 ) {
// Scroll down
} else if( diffY>0 ) {
// Scroll up
} else {
// First scroll event
}
scrollFunc.x = xMethod;
scrollFunc.y = yMethod;
}
window.onscroll=scrollFunc​
Related
useEffect(() => {
const maybeHandler = (event: MouseEvent) => {
menuData.forEach((el) => {
if (el.hasActiveDropdown && event.clientY > 50) {
handleCloseDropDown();
// handleDropDown('0');
}
});
};
document.addEventListener('mousedown', maybeHandler);
return () => document.removeEventListener('mousedown', maybeHandler);
}, [handleCloseDropDown, menuData]);
I am used this useEffect to handle mulltip dropdowns in navbar component,
navbar has fix height 50px so my logic is whenver use click outside the navbar the drop downs all are close.
I am unadble to test in JEST this clientY propery
For my project I basically had a function that detected when the scroll happens This Project Was In React TypeScript
window.addEventListener('wheel', onScroll);
window.addEventListener('touchstart', handleTouchStart);
window.addEventListener('touchmove', handleTouchMove);
and it calls back to onScroll
function onScroll(e:any) {
if(Math.abs(e.deltaY) <= 35 || Scrolled) return;
Scrolled = true;
//console.log(e.deltaY);
let direction = e.deltaY;
//console.log(page);
if(direction > 0) page++;
if(direction < 0) page--;
//console.log(page);
if(page <= 0) page = (0);
if(page >= (pagesRef.current.length-1)) page = (pagesRef.current.length-1);
pagesRef.current[page].scrollIntoView({behavior: "smooth"});
setTimeout(()=>{Scrolled=false},800);
}
and for touch / mobile devices
function handleTouchStart(evt:any) {
const firstTouch = getTouches(evt)[0];
xDown = firstTouch.clientX;
yDown = firstTouch.clientY;
};
function handleTouchMove(evt:any) {
if ( ! xDown || ! yDown ) {
return;
}
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
if ( xDiff > 0 ) {
/* right swipe */
} else {
/* left swipe */
}
} else {
if ( yDiff > 0 ) {
page++;
} else {
page--;
}
}
if(page <= 0) page = (0);
if(page >= (pagesRef.current.length-1)) page = (pagesRef.current.length-1);
pagesRef.current[page].scrollIntoView({behavior: "smooth"});
/* reset values */
xDown = null;
yDown = null;
};
Hope this can help
I'm facing some problem to scroll the HTML sections with the help of viewport jQuery plugin. I have 4 sections on a page, I want to scroll one by one while mouse scrolling (up or down). My HTML and JavaScript code are given below:
<section id="chapter1-block" class="chapter">
<span id="chapter1" class="pointer"></span><span class="pointer-b"></span>
</section>
<section id="chapter2-block" class="chapter">
<span id="chapter2" class="pointer"></span><span class="pointer-b"></span>
</section>
<section id="chapter3-block" class="chapter">
<span id="chapter3" class="pointer"></span><span class="pointer-b"></span>
</section>
<section id="chapter4-block" class="chapter">
<span id="chapter4" class="pointer"></span><span class="pointer-b"></span>
</section>
JS:
$(function () {
var _top = $(window).scrollTop();
var _direction;
var curChapterPos = 'chapter1';
$(window).scroll(function(){
var _cur_top = $(window).scrollTop();
console.log(curChapterPos);
if(_top < _cur_top)
{
if(curChapterPos == "chapter1")
{
$('body').scrollTo( '#chapter2');
curChapterPos = 'chapter2';
console.log(_cur_top);
return false;
}
else
if(curChapterPos == "chapter2")
{
$('body').scrollTo( '#chapter2');
curChapterPos = 'chapter3';
console.log('3--'+curChapterPos);
return false;
}else
if(curChapterPos == "chapter3")
{
$('body').scrollTo( '#chapter3');
curChapterPos = 'chapter4';
console.log('3--'+curChapterPos);
return false;
}
_direction = 'down';
}
else
{
_direction = 'up';
}
_top = _cur_top;
});
});
The problem here, is when the page scrolls, the functions is invoked 7 times if you use the scroll bar from the browser. 9 times with the key down. And If you use the mouse wheel the function is invoked between 12 and 25 times. So, the problem could be solved if you avoid the use of the mouse, because the sensibility of the mouse could be different in any client/browser. But this not resolve the original question. Maybe if combine the page scroll with the mouse hover and the visibility of the tag, will result in the answer you need.
Here is my partial solution. It makes a loop if the mouse scroll goes down.
http://jsfiddle.net/manueru_mx/Wm6Pn/
$(function () {
var _top = $(window).scrollTop();
$("#xtop").val(0);
var _direction;
var curChapterPos = '';
var _last = false;
$(window).scroll(function(){
var _cur_top = $(window).scrollTop();
var _top = $("#xtop").val();
curChapterPos = $("#chapterhidden").val();
if(_top < _cur_top){
if(curChapterPos == "chapter1"){
$('body').scrollTo( '#chapter2');
curChapterPos = 'chapter2';
$("#chapterhidden").val(curChapterPos);
}else if(curChapterPos == "chapter2"){
$('body').scrollTo( '#chapter2');
curChapterPos = 'chapter3';
$("#chapterhidden").val(curChapterPos);
}else if(curChapterPos == "chapter3"){
$('body').scrollTo( '#chapter3');
curChapterPos = 'chapter4';
$("#chapterhidden").val(curChapterPos);
} else if(curChapterPos == "chapter4" && elementInViewport(document.getElementById('chapter3')) == false){
console.log("aquisss");
$('body').scrollTo( '#chapter4');
curChapterPos = 'chapter4';
$("#chapterhidden").val(curChapterPos);
$("#xtop").val(_cur_top+1000);
}else{
return false;
}
_direction = 'down';
$("#xtop").val(_cur_top);
}else {
if(curChapterPos == "chapter4" && elementInViewport(document.getElementById('chapter4-block')) == false){
$('body').scrollTo( '#chapter3');
curChapterPos = 'chapter3';
$("#chapterhidden").val(curChapterPos);
} else if (curChapterPos== "chapter3"){
$('body').scrollTo( '#chapter2');
curChapterPos = 'chapter2';
$("#chapterhidden").val(curChapterPos);
} else if (curChapterPos== "chapter2"){
$('body').scrollTo( '#chapter1');
curChapterPos = 'chapter1';
$("#chapterhidden").val(curChapterPos);
}
_direction = 'up';
$("#xtop").val(_cur_top);
}
});
});
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
When you enter the last position, although the direction of travel is up, never enters the condition. So I add 1000 to the end position. This creates the cycle and lets start from the beginning. It is wrong, but i can not find logic in behavior. Interestingly only happens in the last position.
Tested on FF 16 and IE 8 with compatibility view. and works with mouse scroll, key down and browser scroll bar.
Using this plugin would be easier.
http://www.appelsiini.net/projects/viewport
But I would not change much your initial approach. I used the library scrollTo because I see you use that function and is not native to jQuery
http://demos.flesler.com/jquery/scrollTo/js/jquery.scrollTo-min.js
Hope this help
References:
ElementInViewPort function
I have a menu bar on the top of my page which i want to show if i scroll up while already on top. Imagine you scroll up, and while beeing there, you scroll up again, the bar would show.
I have this code using jquery
// listen for the scroll to show the top menu
var was_on_top = false;
$(function () {
$(window).bind("scroll", function (e) {
if ($(window).scrollTop() == 0) {
if ($('.menu').css('display') == "none" && was_on_top) {
$('.menu').slideDown();
} else {
was_on_top = true;
}
} else {
was_on_top = false;
}
});
});
But the scroll event does not fire while already on top. Any ideas?
EDIT: Here goes the working code. Tested on FF, Chrome and IE9
// listen for the scroll to show the top menu
var was_on_top = false;
$(function () {
if (window.addEventListener) {
window.addEventListener('DOMMouseScroll', onMouseWheelSpin, false); // FireFox
window.addEventListener('mousewheel', onMouseWheelSpin, false); // Chrome
} else {
window.onmousewheel = onMouseWheelSpin;
}
});
function onMouseWheelSpin(event) {
if (!event) // IE sucks
event = window.event;
if ($(window).scrollTop() == 0 &&
(
event.detail < 0 // Firefox
||
(event.wheelDelta && (
(window.opera && event.wheelDelta < 0) // Opera
||
event.wheelDelta > 0 // IE
)
)
)
) {
if ($('.menu').css('display') == "none" && was_on_top) {
$('.menu').slideDown();
} else {
was_on_top = true;
}
} else {
was_on_top = false;
}
}
The scroll event does not fire, because the window is no longer scrolling.
You will likely need to create a custom event for the mouse wheel itself.
This might be helpful: Javascript: Capture mouse wheel event and do not scroll the page?
I'm trying to check to make sure an item is visible before I start working on it using the following function
isVisible: function (node, doc, x, y) {
var el = doc.elementFromPoint(x, y);
if (node === el) return true;
else return false;
},
x and y are positions of the selected node and is calculated by
findPos: function (node) {
var pos = new Object();
pos.left = pos.top = 0;
if (node.offsetParent) {
do {
pos.left += node.offsetLeft;
pos.top += node.offsetTop;
} while (node = node.offsetParent);
}
return pos;
}
Everything works fine. However, when I scroll the page down, the isVisible function is no longer returning the right value. This is caused by the position having changed but the find position function not returning the right value.
Is there a method to get the position of an element like the reverse of elementFromPoint? Or does anyone have another method?
I just wrote an isVisible() method that uses the elementFromPoint() method and should work in IE9+ to detect if an element is visible.
var isVisible = function(elem) {
var w = window, d = document, height, rects, on_top;
if(!elem || (elem && elem.nodeType !== 1) || !elem.getClientRects || !d.elementFromPoint || !d.querySelector || !elem.contains) {
return false;
}
if (elem.offsetWidth === 0 || elem.offsetHeight === 0) {
return false;
}
height = w.innerHeight ? w.innerHeight: d.documentElement.clientHeight;
rects = elem.getClientRects();
on_top = function(r, elem) {
var x = (r.left + r.right)/2,
y = (r.top + r.bottom)/2,
elemFromPoint = d.elementFromPoint(x, y);
return (elemFromPoint === elem || elem.contains(elemFromPoint));
};
for (var i = 0, l = rects.length; i < l; i++) {
var r = rects[i],
in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height);
if (in_viewport && on_top(r, elem)) {
return true;
}
}
return false;
};
You would call it like this: isVisible(document.getElementById('at-follow'));
Also, here is the gist of it.
I was able to fix this by adding window.scrollX and window.scrollY to the doc.elementFromPoint() input parameters x and y
isVisible: function (node, doc, x, y)
{
var el = doc.elementFromPoint(x-window.scrollX, y-window.scrollY);
if (node === el) return true;
else return false;
},
this seems to work fine
This library should be what you're looking for: https://github.com/cpatik/within-viewport/blob/master/withinViewport.js
I was hoping to get a little help with my code. This works in moz/webkit but not in ie. I don't quite understand why :(
$(window).trigger('hashchange');
// Add .selected class to nav on page scroll
var $sections = $('section');
var $navs = $('nav > ul > li');
var topsArray = $sections.map(function() {
return $(this).position().top - 50;
}).get();
var len = topsArray.length;
var currentIndex = 0;
var getCurrent = function( top ) {
for( var i = 0; i < len; i++ ) {
if( top > topsArray[i] && topsArray[i+1] && top < topsArray[i+1] ) {
return i;
}
}
};
$(document).scroll(function(e) {
var scrollTop = $(this).scrollTop();
var secondSection = topsArray[1];
if(scrollTop <= 200) { // moved past the header
$navs.eq(0).removeClass("selected")
} else if(scrollTop >= 205 && scrollTop <= secondSection ) { // between header and 2nd section
$navs.eq(0).addClass("selected")
}
var checkIndex = getCurrent( scrollTop );
if( checkIndex !== currentIndex ) {
currentIndex = checkIndex;
$navs.eq( currentIndex ).addClass("selected").siblings(".selected").removeClass("selected");
}
});
IE is not very forgiving of javascript errors. Try adding some missing semicolons:
if(scrollTop <= 200) { // moved past the header
$navs.eq(0).removeClass("selected"); //missing semicolon
} else if(scrollTop >= 205 && scrollTop <= secondSection ) { // between header and 2nd section
$navs.eq(0).addClass("selected"); //missing semicolon
}
If it is IE only, then IE is having trouble parsing the js. Try running your js through a debugger like JsLint if you run into issues.