Reputation: 7721
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
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:
This also means that Blink and Gecko (Chrome, Firefox) now adhere to the specs while Webkit (Safari) has an open bug
Upvotes: 1
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
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:
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