Remove stickiness after scrolling element - javascript

It is pretty simple to make some element sticky after scrolling several pixels. However, how would you do an opposite of that?
I want an element to be sticky, but after scrolling e.g. 400px (to its original position) it would remain there.
A very good example can be found here http://ultrahd-3d-televize.heureka.cz

You can achieve this by changing position to fixed and absolute via jquery,
use fixed when you want sticky to move along and absolute when it should stop moving. you should also set top of sticky to make it stop at the right place:
var num = 400; //after num pixels, sticky doesn't move any more
$(window).bind('scroll', function () {
if ($(window).scrollTop() > num) {
var top = $(window).height() + num;
$('.menu').css({"position":"absolute","bottom":"auto","top":top + "px"});
} else {
$('.menu').css({"position":"fixed","bottom":"0","top":"auto"})
}
});
This Fiddle shows how to make it happen.

It took a while for me to write my answer, because I used the opportunity to learn about debouncing, to prevent that the piece of code that checks on scroll gets called every time one scroll is done (in theory flooding your browser).
JSFIDDLE
My jQuery:
var menuHeight = $('.menu').height();
console.log(menuHeight);
var scrollingMachine = debounce(function() {
var $this = $(this);
if($(document).scrollTop() > (menuHeight - 850)) {
console.log($(document).scrollTop() - 850);
$('.stickypart').addClass('absolute');
}
else {
$('.stickypart').removeClass('absolute');
}
}, 100);
window.addEventListener('scroll', scrollingMachine);
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Debouncing helps by only calling the function after the scrolling stops, with a maximum of once every 100ms. (just change the 100 to something else if you want it to react faster).
It's a good idea to debounce all functions that get triggered on scroll or for example resizing, to prevent the browser from calculating for every pixel scrolled or resized and only firing when the user is done scrolling or resizing. It can also be used in the case of typing or AJAX calls. Especially in the case of AJAX calls, you want to only fire a function when necessary, not whenever the user lifts his finger from a letter. See an example here.

Related

How to provide a dynamic (calculated) css property

I am working on task in which each time a user scrolls or resizes the screen I would like to recalculate a css properties for an element.
Let's say I want to impl. a progress bar and the progress is reported based on the scroll position in the window
<div class='progress-bar-wrap'>
<div class='progress-bar-progress'></div>
</div>
function updateScrollProgress () {
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
I tried to hook on scroll and resize events, but this seem to have a laggy effect.
window.on('scroll', updateScrollProgress)
window.on('resize', updateScrollProgress)
I tried in the scroll and resize event handlers to requestAnimationFrame
window.on('scroll', function(){window.requestAnimationFrame( updateScrollProgress))
window.on('resize', function(){window.requestAnimationFrame( updateScrollProgress))
And experienced huge improvement in most browsers, however it is yet occasionally laggy.
I tried to request another frame, when from the requestAnimationFrame handler:
function updateScrollProgress () {
window.requestAnimationFrame( updateScrollProgress)
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
This completely eliminated the laggy effect, but comes to the cost of endless loop of calls to this method, even when no recalculations are needed.
Is there a way to hook a handler just before the browser decides to draw element(s), so that I can provide/set the those "dynamic" css values for a property?
What's what you're doing when you use requestAnimationFrame. If you've gotten rid of the lag using it, it's unclear why you say the function is running "too often." Usually in this sort of context, "too often" means "is causing lag" (by running too often and slowing things down). If yours isn't, then...?
If you want the handler called less often, you can debounce it, but then you'll probably notice delays before the changes you want (because you've debounced), which it sounds like is what you're trying to avoid.
In any case, requestAnimationFrame is, for now at least, the right tool for the "just before the browser renders the frame" job.
Wrap your event function through a debounce function:
More info on debounce functions here:
https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('resize', myEfficientFn);

fire a function after window resize

I'm doing a few functions on $(window).resize(). One of the functions inside it, is a complex animation with lots of divs.
$window.resize(function() {
// some functions
doAnim();
}
the problem here is, that the resize() function triggers a lot. Is it possible to fire one function after the resize() is finished, that it doesnt fire a hundred times?
Cheers
So, I believe this is a similar scenario when a user "type" something: you can't know when the user finished to compose a sentence, the same here, you don't know when the user has finished to resize; so what you can do is have an acceptable interval:
var idt;
$(window).resize(function() {
if (idt) clearTimeout(idt);
idt = setTimeout(doAnim, 500);
}
You can also use a closure to avoid to pollute the global scope with idt variable.
However the main point here is that every time the user resize the window, it set a timeout and clear the previous one: the doAnim function will be executed only if the user leave the window without resizing it for half second. Of course you can set your own delay.
You can achieve it as shown below. This will call when you finish with resizing :
var myTimer;
$(window).resize(function()
{
// some functions
var interval = 500;
clearInterval(myTimer);
myTimer = setInterval(function () { doAnim(); }, interval);
});
function doAnim()
{
clearInterval(myTimer);
alert('Resized');
}

Track viewport offset to top

I have a couple of events that get fired depending on how far the user is on the page. Right now I'm using this
$(window).on({
scroll: function() {
trigger_scrolled();
}
});
I have been fiddling with the idea of check every X amount of milliseconds but I don't know how they compare.
Right now the app but it is very memory consuming. Is there a faster way to do this? or any other alternative?
You can limit the amount a function is being called by using a throttle/debouncing mechanism. Underscore.js has one, use a jQuery plugin, or write your own
Use JQuery scrollTop()
var allowed = true;
var timeoutID;
$(window).scroll(function () {
if (!allowed) return;
allowed = false;
if ($(document).scrollTop() > 1000) {
alert("Do stuff");
}
timeoutID = window.setTimeout(function(){allowed = true}, 3000);
});
See fiddle: http://jsfiddle.net/K6aRw/1/
Edit: added a time-out to make it check less oft.

Javascript while loop changing scroll position of div crashes site

I have a div displaying some horizontally scrollable images with white-space:nowrap; overflow-x:scroll and i'm trying to make the function below work:
var mouseIsInDiv = false;
function autoScroll() {
var i = 1;
while (mouseIsInDiv = false) {
setTimeout(function(){
document.getElementById("theDiv").scrollLeft = i;
i++;
},50);
}
}
It is supposed to loop through (while the mouse is not within this scrollable div) incrementing the scroll position by 1px every 50 miliseconds. In other words it's supposed to scroll through the images automatically when this function is called. I'm not getting any syntactic errors but whenever i press a button that calls this function on a webpage, the browser crashes completely - I'm using the latest versions of Chrome, Safari and Firefox. Any ideas would be really helpful, I've been tearing my hair out over this!
Your loop creates many timeouts that happens in the same time (after 50 milisecs) you need to set the timeout recursivly, inside the set timeout function, and ask if mouseISInDiv inside the set timeout function as well.
The current code state, the loop will run many many times in a small amount of time, and page will crush(it's liek infinite) and after 50 millisecs there will be many set timeouts that ran.
I had a fun time working on this one, so I'll post my response despite the correct answer already having been accepted.
Basically, you need to restructure everything so that the whole scheme is asynchronous. That means event listeners respond to mouse movement, and there are no while loops.
Thus, I present this fiddle. Here is the javascript:
var mouseIsInDiv = false;
var theDiv = document.getElementById("theDiv");
theDiv.onmouseover = function() { mouseIsInDiv = true; };
theDiv.onmouseout = function() {
mouseIsInDiv = false;
scrollLeft1();
};
function scrollLeft1() {
if (mouseIsInDiv == false && theDiv.scrollLeft < theDiv.clientWidth) {
theDiv.scrollLeft += 1;
setTimeout(scrollLeft1, 50);
}
}
scrollLeft1();
As you can see, the function calls itself recursively and asynchronously, and the whole thing can be restarted after manually resetting the scroll. You could also add an event listener for the scroll completion.

run command only at the first occurrence of the window resize

I would like to know if the following situation it's possible. Let's say I have the next $.resize() function:
$(window).resize(function(){
if($(window).width() < 500) console.log('Small');
});
I know that the above will log Small as long as the size of the window is under 500. So this means if you resize your window from 500 to 200, the Small will be logged about 300 times.
Well, that means if you actually have some heavy function running under those circumstances, the browser might crash because it will run over and over again until you stop resizing or the circumstances change.
So that is why I'm asking if there would be a way to run that hypothetical function only at the first occurrence of the condition ?
Use the one event bind which runs the passed in function once only at the first occurrence of the event.
$(window).one("resize", function(){
if($(window).width() < 500) console.log('Small');
});
You can use one event handler in jquery to do it.
http://api.jquery.com/one/
like this
$(window).one('resize', function(){alert('lol');});
This should work
$(window).resize(function(){
// apply whatever you want to do with it, other conditions etc...
// regardless of what you want to do, the following line unbinds the resize event
// from within the handler.. consider calling this once all your conditions are met
$(window).unbind("resize");
});
this handler executes your code, then unbinds the resize event from window
Edit: removed conditions. The purpose of this code is just to show how to call unbind, apply the conditions that are best for your scenario
Edit 2: Addressing one of the comments, unbind the way I presented on the code above, will ultimately unbind all handlers for the resize event. A better approach would be declaring the event handler in a separate method like this:
var Window_Resize = function(e) {
// code goes here
};
$(window).resize(Window_Resize);
and to unbind, use this
$(window).unbind(Window_Resize)
This way you only remove that specific handler. Other handlers would still be bound to this event.
There are two possible solutions. If you want the handler to be run the very first time you resize the window, you can force it to run only once:
$(window).one('resize', function () {
if ($(window).width() < 500) console.log('Small');
});
But then you have a problem: it literally only runs once. What happens if they resize it again to be large?
A solution is to introduce a "tolerance" zone, where you run your code only if the window has been resized within a certain period of time:
var RESIZE_TOLERANCE = 200; // milliseconds
var last_resize = 0;
$(window).resize(function () {
var current_time = (new Date()).getTime();
if (current_time - last_resize < RESIZE_TOLERANCE) {
return; // stop
}
last_resize = current_time;
if ($(window).width() < 500) console.log('Small');
});
This forces the resize handler to run at maximum five times per second. You can change the tolerance if you so desire.
However, think of this situation: we resize down from 999px to 998px, firing the resize handler. Then we resize from 998px to 200px before the 200ms is up. The issue is that we have missed the resize event.
A much better solution is to keep track of the current small state and only execute your heavy code if the state changes:
var RESIZE_TOLERANCE = 100; // milliseconds
var SMALL_TOLERANCE = 500; // pixels
var last_resize = 0;
var small = $(window).width() < SMALL_TOLERANCE;
$(window).resize(function () {
var current_time = (new Date()).getTime();
if (current_time - last_resize < RESIZE_TOLERANCE) {
return; // stop
}
last_resize = current_time;
var is_small = $(window).width() < SMALL_TOLERANCE;
if (is_small !== small) {
// run expensive code here
small = is_small;
}
});
Now the tolerance is 100ms, which means we won't be recalculating the window's width more than that. (You can remove that or change it if you want.) We only run the expensive code if the state of the screen has changed. I don't know if this is what you're looking for, but you will have to be creative if your requirements are subtly different.
The straightfoward answer is:
$(window).resize(function(){
if($(window).width() < 500 && !ranfirst) {
ranfirst = true;
console.log('Small');
}
});
This probably won't work the way you're thinking, though it does satisfy the phrasing of your question. You might find you need to set a timeout to poll the size of the window and unset ranfirst when the size remains the same for 10 seconds or so.

Categories

Resources