Reputation: 3118
I'm wondering if anyone has an easy solution for this. I'm trying to detect if any part of a HTML
element finds itself outside of the viewport. I've tried utilizing the following code:
$.fn.isOnScreen = function(){
var win = $(window);
var viewport = {
top : win.scrollTop(),
left : win.scrollLeft()
};
viewport.right = viewport.left + win.width();
viewport.bottom = viewport.top + win.height();
var bounds = this.offset();
bounds.right = bounds.left + this.outerWidth();
bounds.bottom = bounds.top + this.outerHeight();
Brought to you by Steven
I can only get this to work when the entire element is not viewable anymore, but I just need to know if part of the element is outside of the viewport.
When the element is outside of the viewport, then I'm putting a different class on it, so that it will shift to the left instead so that it is viewable again.
Something like:
if(elementIsPartiallyOutsideViewport) {
ele.addClass('move-left');
}
Any ideas?
Upvotes: 13
Views: 7321
Reputation: 2227
There is a solution with an Intersection Observer. An advantage of the solution is position-relative calculations are not required.
(function ($) {
class IntersectionDetector {
async isElementPartiallyOutOfViewport(element) {
return new Promise((resolve) => {
const callback = this.handleOutOfViewportObservation.bind(this, resolve);
const options = {
root: null,
threshold: 0,
}
const observer = new IntersectionObserver(callback, options);
observer.observe(element);
})
}
handleOutOfViewportObservation(resolve, [entry], observer) {
const element = entry.target;
observer.unobserve(element);
observer.disconnect();
const ratios = new Map();
ratios.set('inViewportCompletelyRatio', 1);
ratios.set('outOfViewportCompletelyRatio', 0);
ratios.set('actualRatio', entry.intersectionRatio);
const precision = Math.pow(10, 2);
for (const [name, prevRatio] of ratios) {
const nextRatio = precision * prevRatio;
ratios.set(name, nextRatio);
}
const actualRatio = ratios.get('actualRatio');
const inViewportCompletelyRatio = ratios.get('inViewportCompletelyRatio');
const outOfViewportCompletelyRatio = ratios.get('outOfViewportCompletelyRatio');
const isOutOfViewportPartially =
(actualRatio > outOfViewportCompletelyRatio) &&
(actualRatio < inViewportCompletelyRatio);
resolve(isOutOfViewportPartially);
}
}
$.fn.isOnScreen = async function () {
const elements = this;
const promises = elements.map(async (index, element) => {
const detector = new IntersectionDetector();
return await detector.isElementPartiallyOutOfViewport(element);
});
const results = await Promise.all(promises);
return results.every(result => result);
};
}(jQuery));
jQuery(async function ($) {
const isOnScreen = await $('#element-to-check').isOnScreen();
console.log(isOnScreen);
});
The code tested for:
Upvotes: 0
Reputation: 3434
Most of the browsers already support getBoundingClientRect()
method. So you can try the following code.
function isElementInViewport (el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
You simply pass the element to the function and get false
if element is not inside the viewport.
Usage.
if (!isElementInViewport(el)) {
el.addClass('move-left');
}
Edit
Just an addition. You can get more info about getBoundingClientRect()
function and the browser support in here
Upvotes: 22