Corrl
Corrl

Reputation: 7721

Intersection Observer: padding changes 'boundaries' of root element?

When I run the example (in full page view) in Chrome (98) (or Safari 15.3) the first element directly vanishes when scrolling just a little bit, so kind of 'when leaving the non-padding or entering the padding-area'
The options define a rootMargin: '0px 0px 0px 0px' which I thought refer to the root elements border. That's how it behaves in Firefox(97). The inner element is only hidden when it reaches the top.

Is there a way to define the wrapper borders as boundary and not the 'inner padding-border'?

        const wrapper = document.querySelector('#wrapper')
        const firstElem = document.querySelector('#first-elem')
        const options = {
            root: wrapper,
            rootMargin: '0px 0px 0px 0px',
            threshold: 1
        }
        const observer = new IntersectionObserver(handleFade, options);
        observer.observe(firstElem)

        function handleFade(entries) {
            entries.forEach(entry => {
                let target = entry.target
                if (entry.isIntersecting) {
                    target.classList.remove('fade-out')
                } else {
                    target.classList.add('fade-out')
                }
            })
        }
body {
        padding: 0;
        overflow: hidden;
    }
    #wrapper {
        height: 100vh;
        padding-top: 10rem;
        overflow: auto;
        border: 1px solid darkmagenta;
        box-sizing: border-box;
    }
    .elem {
        border: 3px solid teal;
        padding: 0 2rem;
        height: 20rem;
    }
.fade-out {
        visibility: hidden;
    }
<div id="wrapper">
    <div class="elem" id="first-elem">first watched element</div>
    <div class="elem">element</div>
    <div class="elem">element</div>
    <div class="elem">element</div> 
</div>

Upvotes: 2

Views: 2409

Answers (3)

cuginoAle
cuginoAle

Reputation: 11

Just wanted to mention that on the 18/10/23 the specs have been updated: https://github.com/w3c/IntersectionObserver/pull/517/files

The intersection area is now the padding-area rather than the content-area.

That means that if you have some paddings in the root element and you want the interesctionObserver to take them into account then you need to set the rootMargin prop accordingly.

The combination of paddings and rootMargin is useful when you want to implement things like the following carousel, where the "active" area of a scrollable element is a fraction of its width:

carousel

This also means that Blink and Gecko (Chrome, Firefox) now adhere to the specs while Webkit (Safari) has an open bug

Upvotes: 1

Corrl
Corrl

Reputation: 7721

Given that the scrolled wrapper element takes up the full height of the page, a different solution would be to simply remove root: wrapper from the options, so the elements position is watched in relation to the viewport

const wrapper = document.querySelector('#wrapper')
        const firstElem = document.querySelector('#first-elem')
        const options = {
            rootMargin: '0px 0px 0px 0px',
            threshold: 1
        }
        const observer = new IntersectionObserver(handleFade, options);
        observer.observe(firstElem)

        function handleFade(entries) {
            entries.forEach(entry => {
                let target = entry.target
                if (entry.isIntersecting) {
                    target.classList.remove('fade-out')
                } else {
                    target.classList.add('fade-out')
                }
            })
        }
body {
        padding: 0;
        overflow: hidden;
    }
    #wrapper {
        height: 100vh;
        padding-top: 10rem;
        overflow: auto;
        border: 1px solid darkmagenta;
        box-sizing: border-box;
    }
    .elem {
        border: 3px solid teal;
        padding: 0 2rem;
        height: 20rem;
    }
.fade-out {
        visibility: hidden;
    }
<div id="wrapper">
    <div class="elem" id="first-elem">first watched element</div>
    <div class="elem">element</div>
    <div class="elem">element</div>
    <div class="elem">element</div> 
</div>

Upvotes: 0

Arad
Arad

Reputation: 12814

That's right. This might be a bit confusing but it seems to be the expected behavior according to the specs.

When you set a root for an IntersectionObserver instance, the so-called intersection rectangle — the area that the intersection is checked against — will be the content area of the root element.

The content area refers to the innermost area of an element, which excludes all the paddings:

enter image description here

As for the solution to this problem, you could simply have an extra wrapper element that you set the paddings on, and you remove any paddings from the root element:

const wrapper = document.querySelector('#wrapper');
const firstElem = document.querySelector('#first-elem');

const observer = new IntersectionObserver(entries => {
    entries[0].target.classList.toggle('fade-out', !entries[0].isIntersecting)
}, {
    root: wrapper,
    threshold: 1,
});
  
observer.observe(firstElem)
body {
    padding: 0;
    margin: 0;
    overflow: hidden;
}

#wrapper {
    height: 100vh;
    overflow: auto;
    box-sizing: border-box;
}

#content {
    border: 1px solid darkmagenta;
    padding: 20px;
    padding-top: 10rem;
}

.elem {
    background-color: #eee;
    border: 2px solid blue;
    padding: 20px 30px;
    height: 100px;
    margin-bottom: 20px;
}

.fade-out {
    visibility: hidden;
}
<div id="wrapper">
  <div id="content">
    <div class="elem" id="first-elem">first watched element</div>
    <div class="elem">element</div>
    <div class="elem">element</div>
    <div class="elem">element</div> 
    <div class="elem">element</div> 
    <div class="elem">element</div> 
    <div class="elem">element</div> 
    <div class="elem">element</div> 
  </div>
</div>

Although your example is a bit strange, in the sense that I'm not sure what you were wanting to do, but regardless, any time you want to neutralize the effect of the paddings on the root but still have paddings around your content, the solution is to declare the paddings on an extra wrapper element.

Upvotes: 2

Related Questions