How do you override scroll functions of a browser in JavaScript?
Functions which I was able to find are scroll, scrollBy and scrollTo (from the window object).
But, if I try to override them using the standard procedure, like any other function, for example, by executing the following code window.scroll = function(parameter){} it does not have any effect on the browser.
So, if I try to scroll the page after executing the above code (for all 3 methods), the window still scrolls.
Are there any other functions which browser uses to scroll? If so, which function should I override to get a custom scrolling behaviour?
I am using Google Chrome (latest version), of course.
The window.scroll, window.scrollTo and window.scrollBy methods do not refer to the native scrolling functionality. They are used to scroll to specific locations on the webpage. If you want to implement a completly custom scrolling functionality/behaviour you need to listen to the wheel event, prevent the default and define your custom behaviour.
Note: changed scroll to wheel event as comment mentioned error
document.addEventListener("wheel", (event) => {
event.preventDefault();
event.stopPropagation();
// now define custom functionality
}, { passive: false });
Instead of window you can also apply it to any other element on the page that has a scrollbar and therefore being scrollable.
Futhermore the wheel event has values for deltaX and deltaY so you can see how much would have been scrolled since the last scroll and you can use the properties x and y to see how much had been scrolled since the beginning.
With Javascript, I'm not sure how to completely prevent the scroll, but you can scroll to a particular location right after they try to scroll, like this:
document.addEventListener('scroll', function(event)
{
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
window.scrollTo(0,0);
});
<h1>Try to Scroll</h1>
<ol>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
</ol>
Notice that despite my attempts to be thorough, you can still see a little scrolling happen before window.scrollTo(0,0); undoes it.
CSS
If all you're trying to do is prevent scrolling, you can use overflow: hidden with CSS:
html,body { overflow: hidden; }
<h1>Try to Scroll</h1>
<ol>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
<li></li><li></li><li></li><li></li><li></li><li></li>
</ol>
Related
I'm working on a web project that has animations and page changes on the scroll ( specifically, scroll direction ) and I've been looking for multiple possible good and reliable solutions.
I've been detecting the scroll direction by detected the window's scrollY with the user's previously saved scrollY that I have saved in a variable. The only problem is that the scroll event doesn't fire when at the top or the bottom of page, even though the content is all absolute/fixed positioned.
I want to turn to the wheel event because of its deltaY values from the event, and it still fires when at the top of bottom of the page so I can remove the scrollbar and keep the body of the page 100vh.
The Mozilla dev docs say:
Don't confuse the wheel event with the scroll event. The default
action of a wheel event is implementation-specific, and doesn't
necessarily dispatch a scroll event. Even when it does, the delta*
values in the wheel event don't necessarily reflect the content's
scrolling direction. Therefore, do not rely on the wheel event's
delta* properties to get the scrolling direction. Instead, detect
value changes of scrollLeft and scrollTop of the target in the scroll
event.
(https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event)
And I'm also curious if the wheel event will work correctly on mobile with touch?
Here's a good example of what I'm trying to replicate: https://reed.be
There is no scrollbar, yet things still happen based on your scrolling.
CanIuse shows full compatibility of the wheel event with modern browsers, and some older versions.
see here -> https://caniuse.com/#feat=mdn-api_wheelevent
I've found a solution that references the wheel event (How to determine scroll direction without actually scrolling), though my question still applies -
How reliable is the wheel event across devices and browsers, including mobile?
I am limited to my own current version browsers and android devices for testing.
You can fool the browser by setting the additional height on the body to match the content width and setting the overflow to scroll. Then use some basic script, to set the scrollLeft property of your container to equal the window scrollY.
You will need to set the height of the body equal to the total width of the panels.
body {
height: 400vh; // 4 panels of 100vw each
...
}
.panel {
width: 100vw;
...
}
JS
const viewPort = document.querySelector('#viewport');
let lastScroll = 0;
window.addEventListener('scroll', (e) => {
let scrollY = window.scrollY;
// scroll the container by and equal amount of your window scroll
viewPort.scrollLeft = scrollY;
lastScroll = scrollY;
});
Rough JSFiddle Demo
I'm trying to make a page set it's horizontal scrolling position in the middle on load.
I've tried calling window.scrollBy (and the others like scrollTo etc) in the componentDidMount function (which I confirmed is being called).
I can't get it to work for horizontal nor vertical scrolling.
The npm package 'react-scroll' did work but only for vertical scrolling.
EDIT:
tech_amity is right that these functions take two arguments: the x and y coordinate.
In my case however the problem was solved by accessing the window.scrollTo() function from the onLoad event on a new parent container div. I'm still not really sure why.
so:
function moveLeft()
{
window.scrollTo(300, 0);
}
//and then in the JSX/html:
<div onLoad={moveLeft}>
//... stuff i want to scroll
</div>
You should give proper co-ordinates in scroll function.
window.scrollTo(x-coord, y-coord);
window.scrollTo(scroll-to-middle, 0);
give x-co-ordinates according to you.
Basically I want to disable target from being scrolled to top if the if condition inside handleScroll function is true. So in other words If the condition is true. the user should not be able to scroll to top of the target element anymore and to be able to scroll to bottom of the element.
Also i don't want overflow hidden workarounds if possible.
target.addEventListener('scroll', e => this.handleScroll(e, sectionRect, offset, target));
handleScroll(event, sectionRect, offset, target) {
if ((sectionRect.top - offset) < target.scrollTop)
console.log('dont scroll', event);
},
I dont think its possible you can see more about scroll event here and also i recomend see this part:
"Since scroll events can fire at a high rate, the event handler
shouldn't execute computationally expensive operations such as DOM
modifications. Instead, it is recommended to throttle the event using
requestAnimationFrame, setTimeout or customEvent, as follows:"
and this part:
"In iOS UIWebViews, scroll events are not fired while scrolling is
taking place; they are only fired after the scrolling has completed.
See Bootstrap issue #16202. Safari and WKWebViews are not affected by
this bug."
Maybe creating a custom scroll can be the answer for you.
...without limiting the scroll inside the iframe or the need to specifically name/tag all scrollable elements.
Imagine google maps widget embedded in parent page. When you zoom in the widget you don't want the parent page to scroll, obviously.
I thought an answer to my previous question solved the problem:
While scrolling inside an iframe, the body doesn't know anything about
what happens there. But when iframe scroller reach the bottom or the
top, it pass scrolling to body.
Cancel the event that propagates from the iframe.
But the solution does not work in Firefox because Firefox will not - by design - propagate events captured by iframe to the parent page, yet strangely it will scroll the parent page. See jsfiddle here.
$('body').bind('mousewheel DOMMouseScroll', onWheel);
function onWheel (e){
if (e.target === iframe)
e.preventDefault();
console.log(e);
}
So, how do I prevent page from scrolling when user zooms content in embedded iframe, in Firefox?
Since it is a bug in Firefox, the workaround is to work directly with the scroll event, instead of the mousewheel / DOMMouseScroll ones.
The way I did: When user enters the mouse over the iframe, I set a flag to true, and when he leaves the mouse out there, I set it back to false.
Then, when user tries to scroll, but the mouse arrow is inside the iframe, I prevent the parent window scrolling. But, unfortunately, you can't prevent the window scrolling with the usual e.preventDefault() method, so we still need another workaround here, forcing the window to scroll exactly to the X and Y positions it was already before.
The full code:
(function(w) {
var s = { insideIframe: false }
$(iframe).mouseenter(function() {
s.insideIframe = true;
s.scrollX = w.scrollX;
s.scrollY = w.scrollY;
}).mouseleave(function() {
s.insideIframe = false;
});
$(document).scroll(function() {
if (s.insideIframe)
w.scrollTo(s.scrollX, s.scrollY);
});
})(window);
I've created an immediately executed function to prevent defining the s variable in the global scope.
Fiddle working: http://jsfiddle.net/qznujqjs/16/
Edit
Since your question was not tagged with jQuery (although inside it, you've showed a code using the library), the solution with vanilla JS is as simple as the above one:
(function(w) {
var s = { insideIframe: false }
iframe.addEventListener('mouseenter', function() {
s.insideIframe = true;
s.scrollX = w.scrollX;
s.scrollY = w.scrollY;
});
iframe.addEventListener('mouseleave', function() {
s.insideIframe = false;
});
document.addEventListener('scroll', function() {
if (s.insideIframe)
w.scrollTo(s.scrollX, s.scrollY);
});
})(window);
Given all the prerequisites, I think the following is the sanest way to make this work in Firefox.
Wrap your iframe with a div which is a little bit shorter to enable vertical scrolling in it:
<div id="wrapper" style="height:190px; width:200px; overflow-y: auto; overflow-x: hidden;">
<iframe id="iframeid" height="200px" width="200px" src="about:blank">
</iframe>
</div>
Now you can center the iframe vertically and re-position it every time
the wrapper receives a scroll event (it will occur when a user tries to scroll away at frame edges):
var topOffset = 3;
wrapper.scrollTop(topOffset);
wrapper.on("scroll", function(e) {
wrapper.scrollTop(topOffset);
});
Combine this with your previous fix for Chrome, and it should cover all major browsers. Here is a working example - http://jsfiddle.net/o2tk05ab/5/
The only outstanding issue will be the visible vertical scrollbar on a wrapper div. There are several ways to go about it, for instance - Hide scroll bar, but still being able to scroll
I think that will solve your problem
it solved mine
var myElem=function(event){
return $(event.toElement).closest('.slimScrollDiv')
}
$(document).mouseover(function(e){
window.isOnSub=myElem(e).length>0
})
$(document).on('mousewheel',function(e){
if(window.isOnSub){
console.log(e.originalEvent.wheelDelta);
if( myElem(e).prop('scrollHeight')-myElem(e).scrollTop()<=myElem(e).height()&&(e.originalEvent.wheelDelta<0)){
e.preventDefault()
}
}
})
replace '.slimScrollDiv' with the element selector you want to
prevent parent scroll while your mouse is on it
http://jsbin.com/cutube/1/edit?html,js,output
I have a phonegap application that uses iOS native scrolling through -webkit-overflow-scrolling in a div. I want to be able to manually halt an ongoing scroll when the user clicks a button (to scroll back to the top of the page). Is this doable?
This is actually very possible when using fastclick.js. The lib removes the 300ms click delay on mobile devices and enables event capturing during inertia/momentum scrolling.
After including fastclick and attaching it to the body element, my code to stop scrolling and go to the top looks like this:
scrollElement.style.overflow = 'hidden';
scrollElement.scrollTop = 0;
setTimeout(function() {
scrollElement.style.overflow = '';
}, 10);
The trick is to set overflow: hidden, which stops the inertia/momentum scrolling. Please see my fiddle for a full implementation of stop scrolling during inertia/momentum.
Unfortunately this is not possible at the moment. The scroll event is triggered only when the scrolling has come to an end. As long as the momentum keeps moving the content no events are fired at all. You can see this in Figure 6-1 The panning gesture in Apple's "Safari Web Content Guide".
I also created a fiddle to demonstrate this behavior. The scrollTop value is set after iOS is done animating.
You can capture a touch event using 'touchstart' instead of 'click', as the click event sometimes doesn't seem to get fired until the momentum scroll completes. Try this jQuery solution:
$('#yourTrigger').on('touchstart', function () {
var $div = $('.yourScrollableDiv');
if ($div.scrollTop() === 0) {
return false; //if no scroll needed, do nothing.
}
$div.addClass('scrolling'); //apply the overflow:hidden style with a class
$div.animate({
scrollTop: 0
}, 600, function () {
$div.removeClass('scrolling'); //scrolling has finished, remove overflow:hidden
});
}
where the 'scrolling' class simply has the CSS property, overflow:hidden, which as #Patrick-Rudolph said, will halt any momentum scrolling in progress.
.scrolling {
overflow: hidden;
}
Note: It's best to use a callback function to tell when your scroll animation finishes, rather than setting a timer function.