Window scroll event causing IE to lag badly, when using mouse wheel - javascript

I have a navigation container near the top of the page that should add or remove the classname "stuck" (switching between position:static and position:fixed) when the page scrolls beyond a certain value. Seems to work fine in FF and Chrome, but of course IE (7,8 and 9) is having trouble.
The page lags heavily (essentially unusable) when scrolling using the mousewheel, although if I grab and drag the horiz scrollbar then the page slides smoothly with no lag.
My searching around revealed that it's probably because IE executes way more scroll events than the other browsers, but I can't figure out exactly how to throttle the number of events being fired. You can see in the code block below that I'm also using a 'scroll stop' solution but I really need to also be able to execute a callback WHILE the user is still scrolling when they go beyond a certain point on the page.
I thought the way I was implementing it was pretty and stripped down and basic, but is there a better way to handle this, at least just for IE?
var scrollValue = 0;
var scrollTimer = false;
$(window).bind('scroll', function(){
scrollValue = $(window).scrollTop();
// SET TIMER DELAY FOR SCROLL-STOP
if (scrollTimer) {
clearTimeout(scrollTimer);
}
scrollTimer = setTimeout(scrollStopped, 25);
// STICK/UNSTICK HEADER
if (scrollValue > 320){
if (!$(stickyWrap).hasClass('stuck')){
$(stickyWrap).addClass('stuck')
}
} else {
if ($(stickyWrap).hasClass('stuck')){
$(stickyWrap).removeClass('stuck');
}
}
});

Down with timeout, up with switch
If you made the jQuery a little more simple, and added a switch to only execute anything once before and after the threshold, it should speed things up nicely.
var header = $('.stickyWrap'),
trig = 320,
go = true;
$(window).bind('scroll', function(){
var scrollValue = $(this).scrollTop();
if ((go && scrollValue > trig) || (!go && scrollValue <= trig)) {//before or after
header.toggleClass('stuck');//toggle class
go ? go = false : go = true;//toggle boolean
}
});
Now it will only try to execute anything only once before and once after it crosses the threshold of 320.
Made A Fiddle >

Related

Horizontal scrolling act for vertical built page

I have built my own web page based on the same concept as this demo: https://ihatetomatoes.net/demos/full-screen-layout-with-skrollr/
The problem is when inexperienced users browse on their mobile units. They immediately try to scroll horizontally mid way through the page.
My intention is to enable horizontal scrolling as it were vertical as well. That would be to interpret "scrolling" right on an iPad would be equal to vertical scrolling downward. In addition to normal vertical scrolling.
Is there any jquery function to enable this ?
I doubt your usability design is the best, because the behaviour of 'inexperienced' users might just be the way normal users are used to interact with an app/webpage. Fancy animations might look cool, but could distract from the content and confuse people.
In case you want to try it anyway, here is what i came up with:
$(window).scroll(function() {
var currPos = $(document).scrollLeft();
var callback = function() {
animationComplete = true;
}
if (lastPos < currPos && animationComplete) {
animationComplete = false;
console.log('scroll right');
$('body, html').animate({scrollTop: 200}, callback);
}
});
If a scrolling event happens, it checks if it was horizontal. If that is the case, a downward animation is triggered. Callback function is there so that no further animations are triggered, while still animating (otherwise it would block the vertical scrolling).
http://codepen.io/TobiObeck/pen/jrKBQw

iOS Safari Overscrolling: Pulling down vs. bouncing

My team and me are developing a web application which bears a fixed header, that doesn't scroll.
In order to handle overscrolling on iOS, we need to detect scrolling in negative direction and reposition the fixed header as static again to make it scroll along with the rest of the page.We do this by binding a jQuery scroll handler to window:
$(window).scroll(function() {
if ($(window).scrollTop() < 0) {
// position static header postioning in order
// let the header behave correctly when overscrolling
}
});
This works well, when the page is manually pulled (dragged) down.
But as every iOS user knows, when scrolling the page from a downwards position with speed up again, it bounces (overscrolls), once it reaches its top.
In this case, our scroll handling doesn't work.
At the moment I can imagine two reasons, why this different behaviour occurs:
Rapid scrolling upwards, and making a page bounce, is too fast for Safari's JS engine to ensure a fluid handling
Is bouncing when scrolling upwards technically the same as manually pulling down a webpage? In respect to $(window).scrollTop() ?
Has anybody some hints how to make my scroll handling work in both cases?
If position:fixed in CSS isn't working for you, then you should try to make a draw loop, and every single time that loop runs, you place a horizontal offset that is equal to how far your user has scrolled.
Basically, your JS should look like this if CSS doesn't work:
var head = document.getElementById("header");
//head now has our header
head.style.position = "relative";
//and now, we can manipulate it's position
function draw(){
head.style.top = window.pageYOffset;
//all that's left to do is do this each and every frame.
}
And if you don't know how to make a draw loop, here's the code:
var frameRate = 60;
var frameCounter = (function(){
var counter = 0;
return function(){
counter ++;
if(counter > frameRate/1000){
counter -= frameRate/1000;
draw();
}
}
})();
setInterval(frameCounter, 1);
This has been resolved in iOS 9.3 New meta tag option
<meta name="viewport"content="width=device-width,shrink-to-fit=no">

Hiding a DIV based on screen height with debouncing

I'm working on a page with a simple side nav that has a fixed position, set top: 30%. What I need to happen is at the bottom of the page I want to fade the menu out so it doesn't overlap the footer, the code below works but I think it is checking on scroll so much it takes to long to calculate when you scroll down fast.
Is there a faster/more lightweight way to calculate when to hide the side-nav? I'm not familiar with debouncing but would it help?
Elements:
.body-container-wrapper - total height of page
.footer-container-wrapper - total height of the footer that we want the nav to be hidden at
.internal-side-nav - the menu position: fixed, top: 30%, right: 0
Example Page: http://hsb1.hubspot.com/profile-page-template
Script:
<script type="text/javascript">
$(document).scroll(function () {
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
</script>
I hadn't heard of debounce so I had to look it up. It could potentially help, but that would be an extra plugin you'd have to include and maintain and it might not work exactly how you want (I didn't see anything indicating that it does "bunches or time frames", just seemed to be bunches, which means it might fire late on you).
Instead, what you could do is throttle it yourself with a little bit of timing.
var scrollTimeInterval = 200; // how often we allow the action to trigger, in ms.
var lastScrollTime = 0;
var scrollTimeoutId = null;
$(document).scroll(function () {
var now = (new Date()).getTime();
var dScrollTime = now - lastScrollTime;
lastScrollTime = now;
if (dScrollTime < scrollTimeInterval) {
// Set a timeout so we catch the last one.
scrollTimeoutId = setTimeout(function() { $(document).scroll(); }, scrollTimeInterval - dScrollTime);
return; // too soon, so we'll skip
}
// Clear any potentially pending timeout.
clearTimeout(scrollTimeoutId);
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
With this, the scroll event simply won't do anything if it hasn't been a certain amount of time since it last triggered. To ensure we catch the last scroll event (the last one before they stopped scrolling), I added a timeout which will trigger the scroll event one last time. We also have to make sure to clear that if we handle another scroll event before it fires (so we don't double up on it).
You can control the time interval it allows with the first variable. If 200ms feels a bit sluggish, you can reduce it to 100ms or 50ms and see if that gives a better balance.
Hope that helps.
Dealing with scroll events, in certain circumstances, there's not much you can do.
Solution 1: setTimeout that cancels itself on each iteration
This method is most efficient I believe, but maybe not ideal for you, because there will still be 300ms in which the sidenav would visually overlap the footer before it fades out.
var scrollTimeout = false;
$(document).scroll(function(){
clearTimeout(scrollTimeout);
var _this = this; // for the setTimeout function to know what "this" is
scrollTimeout = setTimeout(function(){
// your original code block goes here
// BUT, replace "this" with "_this"
}, 300);
});
The above essentially only runs your code when the user has not scrolled for at least 300 milliseconds.
Solution 2: Just good old optimization (not too many tricks)
This solution should hide the sidenav immediately, but still runs on every scroll, just does less
var myScrollEvent = (function(){
var $win = $(window);
var $foot = $('.footer-container-wrapper');
var footer_top = $foot.offset().top; // where on the page the footer begins
var $nav = $('.internal-side-nav');
var nav_height = $nav.height(); // maybe use outerHeight(true)?...who knows, only you
var is_hidden = false;
// this is the actual function we want to run on-scroll
return function(){
// jquery, even on fixed elements, still seems to account for scroll position
// when calculating top offset value, below is the coordinate of the bottom of the nav
var nav_bottom = $nav.offset().top + nav_height;
// if the bottom coord of the nav is lower than the top coord of the footer
if(nav_bottom > footer_top && !is_hidden){
is_hidden = true;
// hide it code
}else if(is_hidden){
is_hidden = false;
// show it code
}
};
})();
$(window).scroll(myScrollEvent);
The idea here is to cache some variables and also do the calculation a slightly different way. Your way doesn't seem by any means wrong, but this is just an alternative. Note that with this method, we're assuming the nav height will never change.
You could always combine the two solutions if you'd like as well.
Also, note that I haven't done any browser-2-browser testing, so if there are any flaws, of course let me know.

Javascript issue with if statements

ive wrote a simple script that isnt working as intentioned:
var prevscroll = 0;
$(document).scroll(function(){
var currscroll = $(document).scrollTop();
if(currscroll > prevscroll){
$("header").toggle();
prevscroll = currscroll;
}
if(currscroll < prevscroll){
$("header").toggle();
prevscroll = currscroll;
}
});
When the page is scrolled down, its meant to hide the header and only show it if its scrolled up however whats happening is as i scroll down its flicking on and off. :|
I think I may of just spotted a silly error! ofcourse its going to keep flickering since prev scroll keeps increasing.
You can't use toggle() the way you are because the scroll event is called many, many times so if it's called more than once with the same value for your if statement, then it will flicker on, then off, then on, then off each time you call .toggle(). You need to explicitly hide or show when your condition is met.
I don't quite follow exactly what you're trying to do with prevscroll, but perhaps this is what you want:
var prevscroll = 0;
$(document).scroll(function(){
var currscroll = $(document).scrollTop();
if(currscroll > prevscroll){
$("header").hide();
prevscroll = currscroll;
}
if(currscroll < prevscroll){
$("header").show();
prevscroll = currscroll;
}
});
This will hide the header anytime you are scrolling down further and show the header as soon as you start to scroll up.

jquery: calculating 'margin-left' or 'left' relative to $(window).scrollLeft() is really jagged in Firefox — using .animate() or .css()

I have a horizontally scrolling website, and I have a block that I want to stay in frame at all times as the user scrolls right. It looks perfectly smooth in webkit browsers, but is crazy jagged in Firefox and I don't really care about IEs.
function fixMyId(){
$('#myId').css({'margin-left': 150 + $(window).scrollLeft()});
}
function fixMyIdAlt(){
$('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
And then I have it triggered on window scroll.
What would be a best way to average out the scrolling, so that maybe every so many seconds or pixels of scrolling it fires the function, or upon stopping the scrolling it animates the block into place? I tried playing with delay() but that doesn't do anything. And this one just looks stupid (plus I have no idea what the overhead of this kind of crunching is):
function fixMyIdStupid(){
window.scrollCounter++;
if(window.scrollCounter % 20 == 0) $('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
So what do I do? setTimeout and setInterval may be required, but those always make my head hurt.
EDIT: Here's a jsfiddle of it in action: http://jsfiddle.net/xsxSq/
The #f0f square is the #myId.
I tried to do such things as well, problem is that the scroll event isn't fired as much as you want. A nice workaround was subscribing the calculation function to the mousemove event, so it triggers A LOT. But on the other hand, I came up with another solution.
Why not turn things around and ask yourself:
Lets make it a position:fixed object and calculate what happens on resize. Because you actually are trying to create a position-x:fixed; and a position-y:absolute;
I actually did the following for the opposite kind of thing. A block that has to be exactly in the middle of the x-document, but in the y it was fixed.
$(document).ready(function ()
{
replaceFixed();
$(window).resize(replaceFixed);
$('#content').ajaxSuccess(replaceFixed);
$(window).scroll(replaceFixed);
function replaceFixed()
{
var jEl = $('#centeredFixedContainer');
var winW = $(window).width();
var docW = $(document).width();
var scrL = $(window).scrollLeft();
var divW = jEl.width();
var result = 0;
// window bigger than the element
if(winW > divW)
{
result = -scrL + ((docW-winW)/2);
}
else
{
result = $('#mainContainer').offset().left - scrL;
}
jEl.css('left',result);
}
});
Copying this code will not give you the solution, but will indicate another way to look at your problem.

Categories