Sticky element issue on mobile safari - javascript

I have an element which I wish to stick to the top of the page when scrolling down. Functionally all of the code works thanks to another user on SO. However when scrolling down on the phone it seems that the sticky element lags behind by a bit. What I mean is the code seems to be calling every single time the parent element is scrolling and it causes hundreds or thousands of adjustments to the sticky element so it causes it to shake a bit.
Here is the code below:
HTML
<div id="scroller-wrapper">
<div id="scroller-anchor"></div>
<div id="scroller" class="row visible-xs-block meal-controls">
My sticky element is here and working
</div>
</div>
JS
$('#scroller-wrapper').scroll(function() {
var $anchor = $("#scroller-anchor");
var $scroller = $('#scroller');
var move = function() {
var st = $(window).scrollTop();
var ot = $anchor.offset().top;
if(st > ot) {
$scroller.addClass('fixedElement');
} else {
$scroller.removeClass('fixedElement');
}
};
$(window).scroll(move);
move();
});
CSS
.fixedElement {
position:fixed;
top:0;
right:0;
width:100%;
z-index:10000;
}

IMO, a possible and more effective solution would be to use position: sticky in CSS and not JS. You need to provide top: 0 as well. Some compatibility is lagging in IE, but it is a viable solution already. Worth to check it out here
If you are worried about old browsers you may add a fallback function in JS, which still be somewhat laggy

what you need to do is to throttle or debounce the call to update the element.
also why are you attaching a scroll listener to window inside of your wrapper scroll handler? that will mean that EVERY time that scroll listener is called, it will attach ANOTHER scroll listener to window.
all you need is the single handler on window, and allow propagation to do the rest.
// A debounce function wraps a function with a setTimeout,
// and then resets that timeout everytime it is called
function debounce(func, delay){
var timeout, that = this;
delay = delay || 300;
return function() {
if(timeout) clearTimeout(timeout)
timeout = setTimeout(function() {
return func.apply(that, arguments)
}, delay)
}
}
// a throttle function ensures that a function isn't
// called more than once every interval
function throttle(fn, interval, shouldDebounce){
var lastCall = 0, debouncedFn;
interval = interval || 300
if(shouldDebounce) debouncedFn = debounce(fn, interval);
return function(){
var now = (new Date()).getTime();
if(now - lastCall < interval)
return debouncedFn && debouncedFn.apply(this, arguments);
lastCall = now;
return fn.apply(this, arguments);
}
}
// create a function to set scroll listeners
function setScroller() {
var $anchor = $("#scroller-anchor"),
$scroller = $('#scroller'),
onMove = function onMove() {
var st = $(window).scrollTop(),
ot = $anchor.offset().top;
if(st > ot) {
$scroller.addClass('fixedElement');
} else {
$scroller.removeClass('fixedElement');
}
},
// Throttle the onMove function to make sure it isn't called too often
throttlededOnMove = throttle(onMove, 300);
$(window).scroll(throttlededOnMove);
}
// attach scroll listener on document ready
$(setScroller)

Related

how to change carousel slide on mousewheel

I'm working on a project and I need to create a vertical carousel that works on scroll (mousewheel) I just need to know how can I handle the sliding on scroll.
I have a function called nextSlide when I call then it get the next slide.
I did something like this (I checked the direction and other stuff but I'm adding the simplest code snippet here)
Note: I did Debounce on my function but it didn't work
el.addEventListener('wheel', (event) => {
event.preventDefault();
nextSlide();
});
The problem here is the event firing on each mouse scroll I just need to handle it on one scroll here is an example from swiper
https://codepen.io/Seamni69/pen/vYgmqVd
What I meant by one scroll is calling the function just one time when scrolling, no matter how much scrolling is.
Created this. if wheelDeltaY is positive, you are scrolling down. If it's negative you are scrolling up.
window.addEventListener('wheel', throttle(scrollDirection, 500));
function scrollDirection(e) {
e.preventDefault();
e.wheelDeltaY > 0
? console.log("DOWN")
: console.log("UP")
e.stopImmediatePropagation();
};
function throttle(func, interval) {
let lastCall = 0;
return function() {
const now = Date.now();
if (lastCall + interval < now) {
lastCall = now;
return func.apply(this, arguments);
}
};
}

auto scroll stopped working when I added an an hover

I have a page that auto scrolls - the function scroll() below worked just fine.
I needed to add an on-hover function - which should pause the scrolling, giving the user control over the scroll.
I added some code to stop scrolling on-hover.
<script>
var theInterval;
function startScroll() {
theInterval = setInterval(scroll, 50);
}
function stopScroll() {
clearInterval(theInterval);
}
$(function () {
scroll();
$('#scrollDiv').hover(function () {
stopScroll();
}, function () {
startScroll();
})
});
function scroll() {
if (document.getElementById('scrollDiv').scrollTop < (document.getElementById('scrollDiv').scrollHeight - document.getElementById('scrollDiv').offsetHeight)) {
-1
document.getElementById('scrollDiv').scrollTop = document.getElementById('scrollDiv').scrollTop + 1
}
else { document.getElementById('scrollDiv').scrollTop = 0; }
}
setInterval(scroll, 50);
</script>
I expected that the extra functions would stop the scrolling when the user hovers over the content.
What happened was that the scrolling simply stopped
You are dropping the interval pointer from your initial call to scroll. setInterval returns an ID to the timer that is running the function at the specified cadence.
Your code is kicking off the scrolling on the last line, but not capturing this timer ID to clear -- so on 1st hover you clear a null pointer in theInterval, then on blur you're starting another timer calling scroll.
You probably notice that it gets faster because 2 logic paths are now adding 1px every 50 ms.
On the last line, you need to also set theInterval to keep track of that call, like:
theInterval = setInterval(scroll, 50)
That should fix it.

Control width expand onClick while setInterval, js,html5

I have setInterval problem. I made something similar to load bar. When I click mouse I fire expanding width of my block called loadBar1
// here preset of interval and loadbar...
var interval = 0;
createLoadBar1 = function() {
loadBar1 = {
// another stuff
width:0,
};
document.onclick = function (mouse) {
interval = setInterval(expandLoadBar1, 60);
}
It's expands by the help of this function:
function expandLoadBar1() {
if(loadBar1.width < 60) {
loadBar1.width++;
}
if (loadBar1.width >= 60) {
loadBar1.width = 0;
clearInterval(interval);
}
}
It's very simple above and works well when I click just once but I start having problems when I click more that one time by mouse clicking, it's logically cause the faster loadBar1.width expanding twice and after second or more mouse click the clearInterval for interval stops working and just continue raising expanding speed when I click more.
You probably need to clear the interval when the user clicks:
document.onclick = function () {
clearInterval(interval);
interval = setInterval(expandLoadBar1, 60);
}

Adding listener for position on screen

I'd like to set something up on my site where when you scroll within 15% of the bottom of the page an element flyouts from the side... I'm not sure how to get started here... should I add a listener for a scroll function or something?
I'm trying to recreate the effect at the bottom of this page: http://www.nytimes.com/2011/01/25/world/europe/25moscow.html?_r=1
update
I have this code....
console.log(document.body.scrollTop); //shows 0
console.log(document.body.scrollHeight * 0.85); //shows 1038.7
if (document.body.scrollTop > document.body.scrollHeight * 0.85) {
console.log();
$('#flyout').animate({
right: '0'
},
5000,
function() {
});
}
the console.log() values aren't changing when I scroll to the bottom of the page. The page is twice as long as my viewport.
[Working Demo]
$(document).ready(function () {
var ROOT = (function () {
var html = document.documentElement;
var htmlScrollTop = html.scrollTop++;
var root = html.scrollTop == htmlScrollTop + 1 ? html : document.body;
html.scrollTop = htmlScrollTop;
return root;
})();
// may be recalculated on resize
var limit = (document.body.scrollHeight - $(window).height()) * 0.85;
var visible = false;
var last = +new Date;
$(window).scroll(function () {
if (+new Date - last > 30) { // more than 30 ms elapsed
if (visible && ROOT.scrollTop < limit) {
setTimeout(function () { hide(); visible = false; }, 1);
} else if (!visible && ROOT.scrollTop > limit) {
setTimeout(function () { show(); visible = true; }, 1);
}
last = +new Date;
}
});
});
I know this is an old topic, but the above code that received the check mark was also triggering the $(window).scroll() event listener too many times.
I guess twitter had this same issue at one point. John Resig blogged about it here: http://ejohn.org/blog/learning-from-twitter/
$(document).ready(function(){
var ROOT = (function () {
var html = document.documentElement;
var htmlScrollTop = html.scrollTop++;
var root = html.scrollTop == htmlScrollTop + 1 ? html : document.body;
html.scrollTop = htmlScrollTop;
return root;
})();
// may be recalculated on resize
var limit = (document.body.scrollHeight - $(window).height()) * 0.85;
var visible = false;
var last = +new Date;
var didScroll = false;
$(window).scroll(function(){
didScroll = true;
})
setInterval(function(){
if(didScroll){
didScroll = false;
if (visible && ROOT.scrollTop < limit) {
hideCredit();
visible = false;
} else if (!visible && ROOT.scrollTop > limit) {
showCredit();
visible = true;
}
}
}, 30);
function hideCredit(){
console.log('The hideCredit function has been called.');
}
function showCredit(){
console.log('The showCredit function has been called.');
}
});
So the difference between the two blocks of code is when and how the timer is called. In this code the timer is called off the bat. So every 30 millaseconds, it checks to see if the page has been scrolled. if it's been scrolled, then it checks to see if we've passed the point on the page where we want to show the hidden content. Then, if that checks true, the actual function then gets called to show the content. (In my case I've just got a console.log print out in there right now.
This seems to be better to me than the other solution because the final function only gets called once per iteration. With the other solution, the final function was being called between 4 and 5 times. That's got to be saving resources. But maybe I'm missing something.
bad idea to capture the scroll event, best to use a timer and every few milliseconds check the scroll position and if in the range you need then execute the necessary code for what you need
Update: in the past few years the best practice is to subscribe to the event and use a throttle avoiding excessive processing https://lodash.com/docs#throttle
Something like this should work:
$(window).scroll(function() {
if (document.body.scrollTop > document.body.scrollHeight * 0.85) {
// flyout
}
});
document.body.scrollTop may not work equally well on all browsers (it actually depends on browser and doctype); so we need to abstract that in a function.
Also, we need to flyout only one time. So we can unbind the event handler after having flyed out.
And we don't want the flyout effect to slow down scrolling, so we will run our flytout function out of the event loop (by using setTimeout()).
Here is the final code:
// we bind the scroll event, with the 'flyout' namespace
// so we can unbind easily
$(window).bind('scroll.flyout', (function() {
// this function is defined only once
// it is private to our event handler
function getScrollTop() {
// if one of these values evaluates to false, this picks the other
return (document.documentElement.scrollTop||document.body.scrollTop);
}
// this is the actual event handler
// it has the getScrollTop() in its scope
return function() {
if (getScrollTop() > (document.body.scrollHeight-$(window).height()) * 0.85) {
// flyout
// out of the event loop
setTimeout(function() {
alert('flyout!');
}, 1);
// unbind the event handler
// so that it's not call anymore
$(this).unbind('scroll.flyout');
}
};
})());
So in the end, only getScrollTop() > document.body.scrollHeight * 0.85 is executed at each scroll event, which is acceptable.
The flyout effect is ran only one time, and after the event has returned, so it won't affect scrolling.

Event when user stops scrolling

I'd like to do some fancy jQuery stuff when the user scrolls the page. But I have no idea how to tackle this problem, since there is only the scroll() method.
Any ideas?
You can make the scroll() have a time-out that gets overwritten each times the user scrolls. That way, when he stops after a certain amount of milliseconds your script is run, but if he scrolls in the meantime the counter will start over again and the script will wait until he is done scrolling again.
Update:
Because this question got some action again I figured I might as well update it with a jQuery extension that adds a scrollEnd event
// extension:
$.fn.scrollEnd = function(callback, timeout) {
$(this).on('scroll', function(){
var $this = $(this);
if ($this.data('scrollTimeout')) {
clearTimeout($this.data('scrollTimeout'));
}
$this.data('scrollTimeout', setTimeout(callback,timeout));
});
};
// how to call it (with a 1000ms timeout):
$(window).scrollEnd(function(){
alert('stopped scrolling');
}, 1000);
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div style="height: 200vh">
Long div
</div>
Here is a simple example using setTimeout to fire a function when the user stops scrolling:
(function() {
var timer;
$(window).bind('scroll',function () {
clearTimeout(timer);
timer = setTimeout( refresh , 150 );
});
var refresh = function () {
// do stuff
console.log('Stopped Scrolling');
};
})();
The timer is cleared while the scroll event is firing. Once scrolling stops, the refresh function is fired.
Or as a plugin:
$.fn.afterwards = function (event, callback, timeout) {
var self = $(this), delay = timeout || 16;
self.each(function () {
var $t = $(this);
$t.on(event, function(){
if ($t.data(event+'-timeout')) {
clearTimeout($t.data(event+'-timeout'));
}
$t.data(event + '-timeout', setTimeout(function () { callback.apply($t); },delay));
})
});
return this;
};
To fire callback after 100ms of the last scroll event on a div (with namespace):
$('div.mydiv').afterwards('scroll.mynamespace', function(e) {
// do stuff when stops scrolling
$(this).addClass('stopped');
}, 100
);
I use this for scroll and resize.
Here is another more generic solution based on the same ideas mentioned:
var delayedExec = function(after, fn) {
var timer;
return function() {
timer && clearTimeout(timer);
timer = setTimeout(fn, after);
};
};
var scrollStopper = delayedExec(500, function() {
console.log('stopped it');
});
document.getElementById('box').addEventListener('scroll', scrollStopper);
I had the need to implement onScrollEnd event discussed hear as well.
The idea of using timer works for me.
I implement this using JavaScript Module Pattern:
var WindowCustomEventsModule = (function(){
var _scrollEndTimeout = 30;
var _delayedExec = function(callback){
var timer;
return function(){
timer && clearTimeout(timer);
timer = setTimeout(callback, _scrollEndTimeout);
}
};
var onScrollEnd = function(callback) {
window.addEventListener('scroll', _delayedExec(callback), false);
};
return {
onScrollEnd: onScrollEnd
}
})();
// usage example
WindowCustomEventsModule.onScrollEnd(function(){
//
// do stuff
//
});
Hope this will help / inspire someone
Why so complicated? As the documentation points out, this http://jsfiddle.net/x3s7F/9/ works!
$('.frame').scroll(function() {
$('.back').hide().fadeIn(100);
}
http://api.jquery.com/scroll/.
Note: The scroll event on Windows Chrome is differently to all others. You need to scroll fast to get the same as result as in e.g. FF. Look at https://liebdich.biz/back.min.js the "X" function.
Some findings from my how many ms a scroll event test:
Safari, Mac FF, Mac Chrome: ~16ms an event.
Windows FF: ~19ms an event.
Windows Chrome: up to ~130ms an event, when scrolling slow.
Internet Explorer: up to ~110ms an event.
http://jsfiddle.net/TRNCFRMCN/1Lygop32/4/.
There is no such event as 'scrollEnd'. I recommend that you check the value returned by scroll() every once in a while (say, 200ms) using setInterval, and record the delta between the current and the previous value. If the delta becomes zero, you can use it as your event.
There are scrollstart and scrollstop functions that are part of jquery mobile.
Example using scrollstop:
$(document).on("scrollstop",function(){
alert("Stopped scrolling!");
});
Hope this helps someone.
The scrollEnd event is coming. It's currently experimental and is only supported by Firefox. See the Mozilla documentation here - https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollend_event
Once it's supported by more browsers, you can use it like this...
document.onscrollend = (event) => {
console.log('Document scrollend event fired!');
};
I pulled some code out of a quick piece I cobbled together that does this as an example (note that scroll.chain is an object containing two arrays start and end that are containers for the callback functions). Also note that I am using jQuery and underscore here.
$('body').on('scroll', scrollCall);
scrollBind('end', callbackFunction);
scrollBind('start', callbackFunction);
var scrollCall = function(e) {
if (scroll.last === false || (Date.now() - scroll.last) <= 500) {
scroll.last = Date.now();
if (scroll.timeout !== false) {
window.clearTimeout(scroll.timeout);
} else {
_(scroll.chain.start).each(function(f){
f.call(window, {type: 'start'}, e.event);
});
}
scroll.timeout = window.setTimeout(self.scrollCall, 550, {callback: true, event: e});
return;
}
if (e.callback !== undefined) {
_(scroll.chain.end).each(function(f){
f.call(window, {type: 'end'}, e.event);
});
scroll.last = false;
scroll.timeout = false;
}
};
var scrollBind = function(type, func) {
type = type.toLowerCase();
if (_(scroll.chain).has(type)) {
if (_(scroll.chain[type]).indexOf(func) === -1) {
scroll.chain[type].push(func);
return true;
}
return false;
}
return false;
}

Categories

Resources