htmlintersection-observer

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>


Solution

  • 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.