first of all I want to say that it's not about content jumping!
I have a navbar and a sidebar which both have absolute position. after user scrolls 100 pixels I change both of them to fixed. but an odd action happens (not always!). wrappers of navbar and sidebar flush for a second. I tested it with different browsers and it does not depend on browser. I tried to reproduce the situation in this fiddle:
https://jsfiddle.net/addxmkgj/
(resize the screen as large as possible it happens in large screens)
-- Edit --
https://codepen.io/anon/pen/dJKBPe
codepen link added too.
Causes
Scrolling can generate scroll events quickly and handlers may need to either throttle scroll events to some extent (e.g. perform code action after scrolling has stopped) or be fairly lightweight functions that can execute quickly.
In addition scroll event handling is not synchronized with page update: if the mouse wheel initiates downward scrolling, scrolling can continue after the wheel is released (and similarly with touch event scrolling). The browser can scroll below a top position of 100px before scroll event handling has had a chance to catch up and change the positioning.
The result is the header jumps down from being partially off-screen to occupy a fixed position at top of screen. The faster the scroll action (or the busier the browser is) the more likely it is that jumping will be noticeable.
A secondary effect in desktop browsing is that when the side bar panel scrolls upwards past top of screen and moves down again, a visible patch of white screen "flashes" momentarily below the side bar before fixed positioning takes effect.
Experimental Remedies
Flashing of the side bar can be reduced but not necessarily fully eliminated, by increasing the height of the container. Changing the height to 150% with visible overflow met with some success:
.side-bar {
position: absolute;
height: 150%;
... /* more declarations */
This may or may not conflict with application requirements.
Some mitigation of navbar jumping can be achieved by using requestAnimationFrame call backs to monitor scrollTop values and change positioning as necessary. This does not use scroll event handling as such:
$(document).ready(function() {
$(window).resize(function() {
if( $(window).width() > 850) {
$('.navbar').css('display', 'block');
} else {
$('.navbar').css('display', 'none');
}
});
scrollTo(0, 0);
var num = 100;
var bAbsolute = true;
function checkScroll() {
var newTop = $(window).scrollTop();
if( bAbsolute && newTop >= num) {
$('.navbar').css('position', 'fixed');
$('.navbar').css('top', '0');
$('.side-bar').css('position', 'fixed');
$('.side-bar').css('top', '0');
bAbsolute = false;
}
if( !bAbsolute && newTop < num) {
$('.navbar').css('position', 'absolute');
$('.side-bar').css('position', 'absolute');
$('.navbar').css('top', '100px');
$('.side-bar').css('top', '100px');
bAbsolute = true;
}
requestAnimationFrame( checkScroll);
}
requestAnimationFrame( checkScroll)
});
This code showed an improvement in jump reduction but was not perfect. It is not particularly a JQuery solution and calls requestAnimationFrame directly.
One option, of course, is to do nothing given browser timing constraints.
Update
This MDN guide for Scroll linked effects explains the root cause problem better than I was able to:
most browsers now support some sort of asynchronous scrolling .... the visual scroll position is updated in the compositor thread and is visible to the user before the scroll event is updated in the DOM and fired on the main thread ... This can cause the effect to be laggy, janky, or jittery — in short, something we want to avoid.
So the absolutely positioned elements can scroll off screen (to some extent) before scroll handlers are notified of a new scroll position.
The solution going forward is to use sticky positioning (see the scroll effects guide above or the CSS position guide. However position:sticky swaps between relative and fixed position so the HTML would need redesigning to accommodate this.
Sticky positioning is also leading edge technology at January 2018, and not yet recommended for production use on MDN. A web search for "JQuery support sticky position" revealed a choice of JQuery plugin support.
Recommendation
Potentially the best-case compromise may be to redesign the HTML to use sticky positioning and include a JQuery plugin that uses native support when available or a polyfill when not - site visitors with supporting browsers will get the best experience, those with older browsers will get functional support.
Related
I've got a custom scrollbar solution (view on CodePen).
The obvious idea is dragging the custom scrollbar should scroll the page.
Try it and see what happens... it's bizarrely janky, and the scrollbar and page scrolling will suddenly snap between points.
The scrolling process is currently in a mousemove handler:
update the scrollbar position visually
window.scrollTo(...) the new position, calculated as viewport top relative to the new scrollbar position
If I comment out the window.scrollTo(...) line, the scrollbar itself then moves perfectly smoothly and sticks with the cursor.
Pertinent code
mousemove(e) {
if (!this.active) return;
this.update(this.getScrollDeltaPositional(e.pageY));
window.scrollTo({top: this.getWindowScrollTop()});
}
update(position, show=true, timer=true, time=0) {
let track = this.getTrackHeight();
this.trackPosition = Math.min(Math.max(position, 0), track);
this.track.style.transform = `translateY(${this.trackPosition}px)`;
}
getWindowScrollTop() {
let scroll = this.getDocumentScroll();
let position = (this.trackPosition / this.root.clientHeight);
return Math.round(scroll * position);
}
(Recommended you view the full source on CodePen)
I presume the scrolling each mousemove is blocking the mousemove events, resulting in the sudden snaps being observed.
How to achieve a smooth scrolling effect on window using a custom scrollbar?
I finally found the answer
After far too many hours of trying everything conceivable to remedy this, I stumbled upon this identical problem: https://css-tricks.com/forums/topic/scrolltop-inexplicably-going-haywire/.
As that user eventually discovered, MouseEvent.pageY (which is what I was using to get scroll position) is
relative to the top edge of the viewport, including scroll offsets.
Therefore, the scroll movement effectively amplifies the mousemove events, causing the scrolling to accelerate exponentially as seen in the demo.
So after half a day of hacking about with this, the fix is a simple Ctrl+H... use MouseEvent.clientY instead.
On some of our pages, we've got some legacy JQuery that detects when a page is scrolling and then sets the height of the main element, to position the footer correctly on both mobile and desktop:
function scrollPage(event){
base.keepMainMarginOverFooter();
// other functions
}
$(window).on('scroll',scrollPage);
This is the offending function:
keepMainMarginOverFooter:function() {
// Adjusts container height so footer is clickable but can also be revealed on mobile
var h = 0;
$('.fp-section.visible').children().each(function() {
h+= $(this).outerHeight()
});
$('#main').css({ height: h });
}
And it seems that there's a bug in Chrome (Version 61.0.3163.100) that seems to trigger if you're actively scrolling during any event which triggers the above code (we also have some JQuery functions that , which automatically takes you to the end of the #main section, ie the bottom of the page. Or at least I guess it's a bug - this doesn't seem to happen in Safari (Version 11.0 12604.1.38.1.7), and it doesn't happen if you wait a couple of seconds after page load before scrolling.
It's tempting to remove this function for something simpler, but it's in the middle of enough dependencies that that wouldn't be trivial.
Is this a known issue with a decent workaround?
So I have two sections of content near the top of my page and I’d like for users who have scrolled down to near the top of the second section to get “scroll snapped” to the top of the second one once they have stopped scrolling.
I think it should be possible using jQuery but I haven’t been able to figure it out. Here are my examples:
Without my attempt: http://codepen.io/jifarris/pen/gaVgBp
With my broken attempt: http://codepen.io/jifarris/pen/gaVgQp
Basically I can’t figure out how to make it try scrolling to the spot only once, after scrolling has stopped. It’s kind of just freaking out.
I love how the recently introduced scroll snap points CSS feature handles scroll snapping and I’d almost prefer to use it – for the browsers that support it, at least – but it seems like it only works for items that take up 100% of the viewport height or width, and it seems like it’s for scrolling within an element, not the page itself.
The top section has a fixed height, so this really can be handled with pixel numbers.
And for reference, here’s the heart of the code from my attempt:
$(function() {
$(document).on('scroll', function() {
var top = $(document).scrollTop();
if (top > 255 && top < 455) {
$('html, body').animate({scrollTop: '356'}, 500);
$('body').addClass('hotzone');
} else {
$('body').removeClass('hotzone');
}
});
});
KQI's answer contains most of the steps required to create a well functioning section-scroll for use in your application/webpage.
However, if you'd just want to experiment yourself, developing your script further, the first thing you'll have to do is add a timeout handler. Otherwise your logic, and therefor scrollAnimation, will trigger every single pixel scrolled and create a buggy bouncing effect.
I have provided a working example based on your script here:
http://codepen.io/anon/pen/QjepRZ?editors=001
$(function() {
var timeout;
$(document).on('scroll', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
var top = $(document).scrollTop();
if (top > 255 && top < 455) {
$('body').animate({
scrollTop: '356'
}, 500);
$('body').addClass('hotzone');
} else {
$('body').removeClass('hotzone');
}
}, 50);
});
});
Good luck!
All right, there are couple of things you gonna have to deal with to get a good result: which are performance, call stack queue, easing.
Performance wise you should drop jQuery animate and use VelocityJs which gives a smoother transition, better frame per second (fps) to avoid screen glitches especially on mobiles.
Call stack: you should wrap whatever logic you have to animate the scrolltop with 'debounce' function, set the delay for let say 500mm and check the scrolling behavior. Just so you know, the 'scroll' listener your using is firing on each pixel change and your script will go crazy and erratic. (It is just gonna be a moment of so many calc at the same time. Debounce will fix that for you)
Easing: make the transition looks cool not just dry snappy movement.
Remember, 'easing' with Velocity starts with 'mina.' i.e.
'Mina.easingFnName'
Finally, your logic could be right, i am in my phone now cannot debug it but try to simplify it and work with a single problem at once, be like i.e.
If ( top > 380 ) // debounce(...)
This may come as a huge surprise to some people but I am having an issue with the IE browser when I am using the $(window).scroll method.
My goal:
I would like to have the menu located on the left retain it's position until the scroll reaches > y value. It will then fix itself to the top of the page until the scroll returns to a < y value.
My error:
Everything seems just fine in Chrome and Firefox but when I go to Internet Explorer it would seem the browser is moving #scroller every time the scroll value changes, this is causing a moving/flickering event.
If someone could point me to a resource or give me a workaround for this I would be very grateful!
Here is a fiddle:
http://jsfiddle.net/CampbeII/nLK7j/
Here is a link to the site in dev:
http://squ4reone.com/domains/ottawakaraoke/Squ4reone/responsive/index.php
My script:
$(window).scroll(function () {
var navigation = $(window).scrollTop();
if (navigation > 400) {
$('#scroller').css('top',navigation - 220);
} else {
$('#scroller').css('top',183);
$('#scroller').css('position','relative');
}
});
You might want to take a look at the jQuery Waypoints plugin, it lets you do sticky elements like this and a lot more.
If you want to stick with your current method, like the other answers have indicated you should toggle fixed positioning instead of updating the .top attribute in every scroll event. However, I would also introduce a flag to track whether or not it is currently stuck, this way you are only updating the position and top attributes when it actually make the transition instead of every scroll event. Interacting with the DOM is computationally expensive, this will take a lot of load off of the layout engine and should make things even smoother.
http://jsfiddle.net/WYNcj/6/
$(function () {
var stuck = false,
stickAt = $('#scroller').offset().top;
$(window).scroll(function () {
var scrollTop = $(window).scrollTop();
if (!stuck && scrollTop > stickAt) {
$('#scroller').css('top', 0);
$('#scroller').css('position','fixed');
stuck = true;
} else if (stuck && scrollTop < stickAt) {
$('#scroller').css('top', stickAt);
$('#scroller').css('position','absolute');
stuck = false;
}
});
});
Update
Switching the #scroller from relative to fixed removes it from the normal flow of the page, this can have unintended consequences for the layout as it re-flows without the missing block. If you change #scroller to use an absolute position it will be removed from the normal flow and will no longer cause these side-effects. I've updated the above example and the linked jsfiddle to reflect the changes to the JS/CSS.
I also changed the way that stickAt is calculated as well, it uses .offset() to find the exact position of the top of #scoller instead of relying on the CSS top value.
Instead of setting the top distance at each scroll event, please consider only switching between a fixed position and an absolute or relative position.All browsers will appreciate and Especially IE.
So you still listen to scroll but you now keep a state flag out of the scroll handler and simply evaluate if it has to switch between display types.
That is so much more optimized and IE likes it.
I can get flickers in Chrome as well if I scroll very quickly. Instead of updating the top position on scroll, instead used the fixed position for your element once the page has scrolled below the threshold. Take a look at the updated fiddle: http://jsfiddle.net/nLK7j/2/
I am trying to re-create website with parallax effect using JavaScript. That means that I have two or more layers, that are moving different speeds while scrolling.
In my case I'm moving only one layer, the other one remains static:
layer 1 = website text;
layer 2 = element background;
for this I'm using simple source code (with jQuery 1.6.4):
var docwindow = $(window);
function newpos(pos, adjust, ratio){
return ((pos - adjust) * ratio) + "px";
}
function move(){
var pos = docwindow.scrollTop();
element.css({'top' : newpos(pos, 0, 0.5)});
}
$(window).scroll(function(){
move();
});
The Problem:
- All calculations are done right and the effect "works" as expected. But there is some performance glitch under some browsers (Chrome MAC/Windows, Opera MAC, IE, paradoxically not Safari).
What do I see during scrolling?
- While scrolling the background moves in one direction together with scroll, but it seems to occasionally jump few pixels back and then forth, which creates very disturbing effect (not fluid).
Solutions that I tried:
- adding a timer to limit scroll events
- using .animate() method with short duration instead of .css() method.
I've also observed, that the animation is smooth when using .scrollTo method (scrollTo jQuery plugin). So I suspect that there is something wrong with firing scroll events (too fast).
Have you observed the same behavior?
Do you know, how to fix it?
Can you post a better solution?
Thanks for all responses
EDIT #1:
Here you can find jsfiddle demonstration (with timer): http://jsfiddle.net/4h9Ye/1/
I think you should be using scrollTop() instead and change the background position to fixed. The problem is that setting it to absolute will make it move by default when you scroll up or down.
The flicker occurs because the position is updated as part of the default browser scroll and updated again as part of your script. This will render both positions instead of just the one you want. With fixed, the background will never move unless you tell it so.
I've created a demo for you at http://jsfiddle.net/4h9Ye/2/ .