This is a Javascript project, using the jQuery library.
I have a sticky bar I would like to show whenever the browser window is below a certain page position, and otherwise hide it.
Its ID is #sticky-atc-bar
.
The page position is determined by the top of an element with class .product-section
.
Based on research into how to do this, I originally came up with the following JS.
$( document ).ready(function() {
$('#sticky-atc-bar').hide();
var section = $(".product-section");
var offsetSection = section.offset().top; //check for top property
$(function() {
$(window).scroll(function() {
console.log('Scroll Event');
if ($(window).scrollTop() >= offsetSection) { // reached product section
console.log('True:');
$('#sticky-atc-bar').show();
} else { // not reached product section
console.log('False');
$('#sticky-atc-bar').hide();
}
});
});
});
This works. Yet, I am aware it generates a lot of scroll events, the entire time the user is engaged with the page. Knowing that's very inefficient, I went looking for alternative approaches. Along the way I read this article called "Learning from Twitter", which made total sense.
I also came across this solution to a similar requirement. My version of that code is:
var target = $(".product-section").offset().top,
timeout = null;
$('#sticky-atc-bar').hide();
$(window).scroll(function () {
if (!timeout) {
timeout = setTimeout(function () {
console.log('scroll');
clearTimeout(timeout);
timeout = null;
if ($(window).scrollTop() >= target) {
$('#sticky-atc-bar').show();
} else {
$('#sticky-atc-bar').hide();
}
}, 250);
}
});
I set it up on this jsFiddle, and it works.
However ... It still generates a significant number of events. They have simply been reduced to one event every 250 ms, so 4 every second.
What I would like to know is if there's a better way to go about this? Put another way, aside from changing the timeout on this second piece of code, can I reduce this operation to even fewer events? And is that even necessary at this point, or is one event every 250ms not a significant impact?
CodePudding user response:
Here you go: https://jsfiddle.net/son0azfj/
var productEl = document.querySelector('.product-section')
var stickyEl = document.querySelector('#sticky-atc-bar')
// check if we have elements
if (productEl && stickyEl) {
// create an observer object, binding toggleDisplayFactory to it
var observer = new IntersectionObserver(toggleDisplayFactory(stickyEl))
// observe the product element
observer.observe(productEl)
}
// [1] create a named function which accepts an element...
function toggleDisplayFactory(element) {
// and returns a function - the handler that IntersectionObserver expects
return function handler(entries) {
// determine if the observed element is in the viewport or not
var isInViewPort = Boolean(entries.find(entry => entry.intersectionRatio > 0))
/**
* if in the viewport:
* we want the element passed in at [1] to use it's own styles, i.e. remove the inline style property
* else:
* we want the element to inherit its default display property (e.g. block, inline, inline-block, etc.) and thus be visible
*/
var display = isInViewPort ? null : 'inherit'
// set the display property of the element passed in at [1]
element.style.display = display
}
}
- use
InterSectionObserver
to watch for the position of an element with respect to the viewport. Use that element's position in the viewport to "do something else", i.e. show / hide another element - avoid
setTimeout
for manipulating the DOM - userequestAnimationFrame
instead (not required here) - avoid querying DOM elements repeatedly - get them once, and reference them where they need to be manipulated
- prefer setting defaults in CSS, and then in JS override a specific property inline, removing the property when you no longer need it
Reasoning:
IntersectionObserver
allows you to observe an arbitrary element with respect to its position in the viewport, instead of a scroll event which fires rapidly as one scrolls. The events an observed element trigger are only triggered when its position relative to the viewport changes
setTimeout
- If you were to use a timeout, you would need to think of some number which is appropriate to use when using setTimeout
. How do you do this? Well, you can only guess... you'd have to use a magic number. requestAnimationFrame
instead knows when the DOM is ready for re-rendering and calculating layout - no magic numbers required
Links:
EDIT: Feel free to replace document.querySelector
with $(selector)
- the rest of the code will work as-is.