The page flickers on scroll, it works fine in Firefox but not in chrome. I tried disabling the chrome smooth scroll plugin but still doesn't work.
If you inspect the source code and search for 'scroll', you will find the following event bindings (unminified & beautified) :
$window.bind('scroll').resize();
$(window).bind('mousewheel DOMMouseScroll', function(event) {
if (/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())) {
var delta = event.originalEvent.wheelDelta;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
};
$("html").stop().animate({
scrollTop: $("html").scrollTop() + (-delta * 1.7)
}, 200, 'linear');
}
});
The resize on scroll is a weird thing, but the second binding is definitely nonsense and is the one causing the issue :
every tiny scroll animation will be interrupted $("html").stop()
and overridden with a contradictory scroll animation .animate({scrollTop: $("html").scrollTop() + (-delta * 1.7)})
Before commenting/deleting this code, just unbind from the chrome console to check :
jQuery(window).unbind('mousewheel DOMMouseScroll');
Related
I've got this jQuery to add a back to top button. It's simple and works very well. I have it running as a plugin in WordPress MultiSite on probably 120 websites. Today I noticed it isn't working on every site. There are no console errors, but my guess is that some other plugin or script is causing a conflict. This is inconsistent from one site to the other and I can't find a reason.
How can I write this jQuery so it doesn't experience compatibility issues?
jQuery(document).ready(function($){
//Check to see if the window is top if not then display button
$(window).scroll(function(){
if ($(this).scrollTop() > 100) {
$(".scrollToTop").fadeIn();
} else {
$(".scrollToTop").fadeOut();
}
});
//Click event to scroll to top
$(".scrollToTop").click(function(){
$("html, body").animate({scrollTop : 0},800);
return false;
});
});
Example site 1: http://anntowergallery.com/exhibits/ Doesn't work.
Example site 2: http://iemajen.com/asphaltanimals/ Works
I've tried this out on a dozen sites or so and cannot pin point what could cause the problem. No errors in console on the gallery website.
I appreciate any feedback.
Strange bug you got there.
Seems that in site 1 you have the following CSS:
body {
overflow-x: hidden;
}
When that CSS is in place, the $(window).scroll event listener won't fire. If you remove that CSS line, the JS works just fine.
You can also bind the scroll event to the body instead of the window:
$("body").scroll(function(){
...
});
But I recall that had some issues with IE. Probably you'd be safest to bind both $("body").scroll and $(window).scroll:
jQuery(document).ready(function($){
//Check to see if the window is top if not then display button
$(window).add("body").scroll(function(){
if ($(this).scrollTop() > 100) {
$(".scrollToTop").fadeIn();
} else {
$(".scrollToTop").fadeOut();
}
});
//Click event to scroll to top
$(".scrollToTop").click(function(){
$("html, body").animate({scrollTop : 0},800);
return false;
});
});
You've got a style element inside the body tag right before your scroll script, which isn't valid and may be preventing the script from executing. Try moving that into the head.
This is the part I'm talking about:
<style type="text/css">
.scrollToTop {
/* ... */
}
</style>
I wouldn't use that code on mobile devices... every tick of the window scroll is firing either a fadeIn or fadeOut. It would be better to add a flag to check if the scroll to top button is visible, or not (demo)
jQuery(document).ready(function($) {
var visible = false;
//Check to see if the window is top if not then display button
$(window).scroll(function() {
var scrollTop = $(this).scrollTop();
if (!visible && scrollTop > 100) {
$(".scrollToTop").fadeIn();
visible = true;
} else if (visible && scrollTop <= 100) {
$(".scrollToTop").fadeOut();
visible = false;
}
});
//Click event to scroll to top
$(".scrollToTop").click(function() {
$("html, body").animate({
scrollTop: 0
}, 800);
return false;
});
});
Why does another scroll event get called after a scrollTop animation fires its complete callback?
Click Handler:
var lock = false;
$('#id').click(function(event) {
var pos;
if (lock) {
return;
}
lock = true;
pos = 150;
console.log("jump start");
$(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: pos }, 150, function () {
lock = false;
console.log("jump end");
});
});
Scroll Handler:
$(window).scroll(function (e) {
console.log("scrolling");
if (!lock){
alert('1');
}
});
Log:
jump start
scrolling
jump end
scrolling
demo on jsfiddle
Background
jQuery scrollTop() uses scrollTo() which is a fire and forget event. There is no stopped event for scrolling. The scroll events occur out of band from scrollTo. scrollTo means 'start scroll', a scroll event means 'scrolled (some position)'. scrollTo just initiates the starting of the scroll, it doesn't guarantee that scrolling finished when it returns. So, jQuery animation completes before final scroll (there could even be multiple scrolls backed up). The alternative would be for jQuery to wait for the position of the scroll to be what it requested (per my soln), but it does not do this
It would be nice if there was a specification that we could point to describing this, but it is just one of the Level 0 dom elements without a spec, see here. I think it makes sense the way that it works, which is why all browsers seem to implement it this way.
Why is this happening
The following occurs on the last scroll of the animation:
jquery: 'Window please scroll this last bit'
Window: 'I got this message from jquery to scroll I will start that now'
jquery: 'woohoo I am finished the animation, I will complete'
Your code: lock = false;console.log("jump end");
Window: 'I have scrolled' call scroll event handlers.'
Your code: $(window).scroll(function (e) { 'Why is this happening?'
As you can see jquery does not wait for the final scroll step of the animation to complete before completing the animation (going on to step 4). Partly this is because there is no stopped event for scrolling and partly this is because jquery does not wait for the scroll position to reach the position that was requested. We can detect when we have reached the destination position as described below.
Solutions
There is no stopped event for when scrolling completes. See here. It makes sense that there is no stopped event because the user could start scrolling again at any point, so there is no point where scrolling has really stopped - the user might just have paused for a fraction of a second.
User scrolling: For user scrolling, the normal approach is to wait some amount of time to see if scrolling is complete as described in the answer of the referenced question (bearing in mind that the user could start scrolling again).
scrollTop: However, since we know the position that we are scrolling to we can do better.
See this fiddle.
The crux of it is that since we know where we are scrolling to, we can store that position. When we reach that position we know that we are done.
The output is now:
jump start
scroll animation
jump end
The code is (note that this is based off your fiddle rather than the code in the edited question):
var scrollingTo = 0;
$('#id').click(function(event) {
if (scrollingTo) {
return;
}
console.log("jump start");
scrollingTo = 150;
$(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: scrollingTo }, 150, function () {
});
});
function handleScroll()
{
if( scrollingTo !== 0 && $(window).scrollTop() == scrollingTo)
{
scrollingTo = 0;
console.log("jump end");
}
}
$(window).scroll(function (e) {
if (!scrollingTo){
console.log('user scroll');
} else {
console.log("scroll animation");
}
handleScroll();
});
I believe that at the time the animation ends and the callback function is called, the event has not reached the window yet, so it is not re-called, it just hasn't been fired yet.
Is it possible to force-stop momentum scrolling on iphone/ipad in javascript?
Extra: pretty sure this is pie in the sky, but for bonuspoints (honor and kudos), after dom-manipulation and a scrollTo applied, resume scroll with the same momentum before the forced stop. How to?
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.
Here is my code using jQuery animation (running as onclick event)
var obj=$('html, body'); // your element
if(!obj.is(':animated')) {
obj.css('overflow', 'hidden').animate({ scrollTop: 0 }, function(){ obj.css('overflow', ''); });
}
Tested on iPhone 6
I have found a way to CANCEL the BODY momentum scrolling by assigning the html.scrollTop property on touchend event. Looks like this:
let html = document.querySelector('html');
window.addEventListener( 'touchend', function( e ){
html.scrollTop = html.scrollTop;
});
Tested on iOS 13
UPD: The above solution fails on iOS 12, because the actual scrolling element is not "html" in this version.
The below code works for Both 12 & 13:
window.addEventListener( 'touchend', function( e ){
window.scroll( 0, window.scrollY );
});
Is there a way to prevent $(window).scroll() from firing on page load?
Testing the following code in Firefox 4, it fires even when I unplug the mouse.
jQuery(document).ready(function($){
$(window).scroll(function(){
console.log("Scroll Fired");
});
});
The scroll event is unrelated to the mouse, it is called whenever a new document scrolling position is set. And arguably that position is set when the document loads (you might load it with an anchor after all), also if the user presses a cursor key on his keyboard. I don't know why you need to ignore the initial scroll event but I guess that you only want to do it if pageYOffset is zero. That's easy:
var oldPageYOffset = 0;
$(window).scroll(function(){
if (window.pageYOffset != oldPageYOffset)
{
oldPageYOffset = window.pageYOffset;
console.log("Window scrolling changed");
}
});
Note: MSIE doesn't have window.pageYOffset property so the above will need to be adjusted. Maybe jQuery offers a cross-browser alternative.
This was the solution I went for. Any improvements gratefully received.
var scroll = 0;
$(window).scroll(function(){
if (scroll>0){
console.log("Scroll Fired");
}
scroll++;
});
The scroll event does not fire on every load, only when refreshing a page that was scrolled, or when navigating to an anchor directly.
Many of the answers suggest ignore the first time it's called, which would ignore a valid scroll if the page doesn't get scrolled initially.
//Scroll the page and then reload just the iframe (right click, reload frame)
//Timeout of 1 was not reliable, 10 seemed to be where I tested it, but again, this is not very elegant.
//This will not fire initially
setTimeout(function(){
$(window).scroll(function(){
console.log('delayed scroll handler');
});
}, 10);
//This will fire initially when reloading the page and re-establishing the scroll position
$(window).scroll(function(){
console.log('regular scroll handler');
});
div {
height: 2000px;
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
</div>
This seems to have worked for me.
$(window).bind("load", function() {
$(window).on("scroll", function () {
console.log('scroll');
});
});
sure, don't load it until after the load. make it a call back for sleep for 500 millis.
I can't seem to capture the scroll event on an iPad.
None of these work, what I am doing wrong?
window.onscroll=myFunction;
document.onscroll=myFunction;
window.attachEvent("scroll",myFunction,false);
document.attachEvent("scroll",myFunction,false);
They all work even on Safari 3 on Windows.
Ironically, EVERY browser on the PC supports window.onload= if you don't mind clobbering existing events. But no go on iPad.
The iPhoneOS does capture onscroll events, except not the way you may expect.
One-finger panning doesn’t generate any events until the user stops panning—an onscroll event is generated when the page stops moving and redraws—as shown in Figure 6-1.
Similarly, scroll with 2 fingers fires onscroll only after you've stopped scrolling.
The usual way of installing the handler works e.g.
window.addEventListener('scroll', function() { alert("Scrolled"); });
// or
$(window).scroll(function() { alert("Scrolled"); });
// or
window.onscroll = function() { alert("Scrolled"); };
// etc
(See also https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
For iOS you need to use the touchmove event as well as the scroll event like this:
document.addEventListener("touchmove", ScrollStart, false);
document.addEventListener("scroll", Scroll, false);
function ScrollStart() {
//start of scroll event for iOS
}
function Scroll() {
//end of scroll event for iOS
//and
//start/end of scroll event for other browsers
}
Sorry for adding another answer to an old post but I usually get a scroll event very well by using this code (it works at least on 6.1)
element.addEventListener('scroll', function() {
console.log(this.scrollTop);
});
// This is the magic, this gives me "live" scroll events
element.addEventListener('gesturechange', function() {});
And that works for me. Only thing it doesn't do is give a scroll event for the deceleration of the scroll (Once the deceleration is complete you get a final scroll event, do as you will with it.) but if you disable inertia with css by doing this
-webkit-overflow-scrolling: none;
You don't get inertia on your elements, for the body though you might have to do the classic
document.addEventListener('touchmove', function(e) {e.preventDefault();}, true);
I was able to get a great solution to this problem with iScroll, with the feel of momentum scrolling and everything https://github.com/cubiq/iscroll The github doc is great, and I mostly followed it. Here's the details of my implementation.
HTML:
I wrapped the scrollable area of my content in some divs that iScroll can use:
<div id="wrapper">
<div id="scroller">
... my scrollable content
</div>
</div>
CSS:
I used the Modernizr class for "touch" to target my style changes only to touch devices (because I only instantiated iScroll on touch).
.touch #wrapper {
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
.touch #scroller {
position: absolute;
z-index: 1;
width: 100%;
}
JS:
I included iscroll-probe.js from the iScroll download, and then initialized the scroller as below, where updatePosition is my function that reacts to the new scroll position.
# coffeescript
if Modernizr.touch
myScroller = new IScroll('#wrapper', probeType: 3)
myScroller.on 'scroll', updatePosition
myScroller.on 'scrollEnd', updatePosition
You have to use myScroller to get the current position now, instead of looking at the scroll offset. Here is a function taken from http://markdalgleish.com/presentations/embracingtouch/ (a super helpful article, but a little out of date now)
function getScroll(elem, iscroll) {
var x, y;
if (Modernizr.touch && iscroll) {
x = iscroll.x * -1;
y = iscroll.y * -1;
} else {
x = elem.scrollTop;
y = elem.scrollLeft;
}
return {x: x, y: y};
}
The only other gotcha was occasionally I would lose part of my page that I was trying to scroll to, and it would refuse to scroll. I had to add in some calls to myScroller.refresh() whenever I changed the contents of the #wrapper, and that solved the problem.
EDIT: Another gotcha was that iScroll eats all the "click" events. I turned on the option to have iScroll emit a "tap" event and handled those instead of "click" events. Thankfully I didn't need much clicking in the scroll area, so this wasn't a big deal.
Since iOS 8 came out, this problem does not exist any more. The scroll event is now fired smoothly in iOS Safari as well.
So, if you register the scroll event handler and check window.pageYOffset inside that event handler, everything works just fine.
After some testing on the ios, I found that this is the way to go for ios and desktop, if you are not worried of that delay of 120ms on desktop. Works like a charm.
let isScrolling;
document.addEventListener("scroll", () => {
// Clear our timeout throughout the scroll
window.clearTimeout( isScrolling );
// Set a timeout to run after scrolling ends
isScrolling = setTimeout(function() {
// Run the callback
console.log( 'Scrolling has stopped.' );
}, 120);
});