I am animating elements of a website once they come into viewport, and I want to have different threshold options for mobile and desktop.
An element that will animate contains class names, for example animation-observer anim-m-40 anim-d-100 anim-bounce-in. Once the element comes into viewport, the class animated is added and the defined animation (in this case anim-bounce-in) runs.
My threshold logic
Threshold can be defined in a dropdown by the steps 0.05, 0.10, 0.15, ... 1.
The above example explained:
- in a mobile viewport
< 600pixels, thethresholdof0.4should be used (defined by the classanim-m-40). - in a desktop viewport of
>= 600pixels thethresholdof1should be used (defined by the classanim-d-100).
When resizing the viewport, the elements are unobservered, and then observed again, depending on the viewport width.
I have a working solution, but...
...it's way too many lines of code! It kind of hurts my eyes, to be honest.
I poured all my Javascript knowledge on how to simplify code using function etc into it, but just couldn't find a better solution, as the IntersectionObserver API is new to me.
The working code
I simplified the code a little by removing some lines and replacing them with // ....
The logic is still in tact:
const observerCallback = (elements, observer) => {
// isIntersecting is true when element and viewport are overlapping
// isIntersecting is false when element and viewport don't overlap
elements.forEach(element => {
if(element.isIntersecting === true) {
let target = element.target;
target.classList.add('animated');
// we're done here. stop observing element
observer.unobserve(target);
}
});
}
// default (fallback) observer
var observer_default = new IntersectionObserver(observerCallback, { threshold: [0.40] });
function initIntersectionObserver () {
// mobile: create observeres in 0.05 steps
window.observer_05 = new IntersectionObserver(observerCallback, { threshold: [0.05] });
window.observer_10 = new IntersectionObserver(observerCallback, { threshold: [0.10] });
// ...
window.observer_95 = new IntersectionObserver(observerCallback, { threshold: [0.95] });
window.observer_100 = new IntersectionObserver(observerCallback, { threshold: [1] });
}
// creating a function to handle viewport change
function initObserverElements(viewport_has_changed = false){
// default (fallback)
let elements_to_observe = document.querySelectorAll('.animation-observer:not([class*="anim-m"]):not([class*="anim-d"]):not([class*="animated"])');
elements_to_observe.forEach(element => { observer_default.observe(element); });
// conditionally unobserve elements
if (viewport_has_changed === true) {
elements_to_unobserve = document.querySelectorAll('.animation-observer[class*="anim-m"][class*="anim-d"]');
elements_to_unobserve.forEach(element => { window.observer_05.unobserve(element); });
elements_to_unobserve.forEach(element => { window.observer_10.unobserve(element); });
// ...
elements_to_unobserve.forEach(element => { window.observer_95.unobserve(element); });
elements_to_unobserve.forEach(element => { window.observer_100.unobserve(element); });
}
// mobile first
if( window.innerWidth < 600) {
elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-05:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_05.observe(element); });
elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-10:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_10.observe(element); });
// ...
elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-95:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_95.observe(element); });
elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-100:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_100.observe(element); });
}else {
// desktop
elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-05:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_05.observe(element); });
elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-10:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_10.observe(element); });
elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-15:not([class*="animated"])');
// ...
elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-95:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_95.observe(element); });
elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-100:not([class*="animated"])');
elements_to_observe.forEach(element => { observer_100.observe(element); });
}
}
// Adding eventlistner for window resize
var resizeTimeout;
function resizeThrottler() {
// ignore resize events as long as an actualResizeHandler execution is in the queue
if ( !resizeTimeout ) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
var viewport_has_changed = true;
initObserverElements(viewport_has_changed);
// The actualResizeHandler will execute at a rate of 15fps
}, 66);
}
}
window.addEventListener("resize", resizeThrottler, false);
// init on page load
initIntersectionObserver();
initObserverElements();
Appreciate any hint!