IntersectionObserver [JS]
Hello,
I'm attempting to use IntersectionObserver() in JS to create a lazy loading effect. My code at the moment is the basic setup. Where I'm stuck at the moment is trying to figure out how to detect the child of the element being observed. Basically, I want to check if .cards is observed and if true, console.log which card is within the intersection.
Thanks π
Code: https://codepen.io/Matt-CopOffMatt/pen/poqZzjP
102 Replies
you're observing the parent, not the children
you have to observe the children
I tried that with:
Uncaught TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.
at HTMLDocument.<anonymous>
weird
That's why I figured I had to do the parent then get children
could this be happening because querySelectorAll returns a nodelist not an array?
There are several problems with your code:
1) None of that code should be inside an event listener for scroll. Doing that would create a new observer for each scroll action.
2) Do not observe the parent when you actually want to know if the children are intersecting. Observe the children instead.
3) You must test if the entry is intersecting. The callback runs on all entries whenever any of them intersects.
4) The thing to log or do anything else with is the entry.target and not the entry.
yeah, that's a good point
i didnt read the code thruougly
why do you have an intersection observer in a scroll event?
entry.target
is used for other observers, by the wayIt wasn't running continously, only when the intersection was found
The problem isn't continuous running. The problem is multiple new observers get created when you don't need them.
Yeah. When I originally ran the test code (without scroll event) it was only returning once
That is because you observed the parent and there is only one of them.
The code you provided though works exactly how I was trying
Wouldn't it return once exiting the parent though ?
What does that even mean?
There is a starting hero section that takes up entire viewport. When I run this code, it instantly returns despite not ever scrolling to cards section
From my understanding of this,
console.log(entry)
should fire once scrolled to .cards
element
but in this snippet video, it fires on page load despite me not scrolling to the .cards
section?Your code doesn't check if it's intersecting and it also doesn't check the intersection ratio.
Ah so basically, what I wrote just check to see if it's within the DOM?
Not exactly. The callback runs once when an element is observed and again when any element being observed is intersecting.
okay
Thanks for the explanation I appreciate it
by the way, don't do lots of processing inside an intersection handler
I'm currently making a simple Lazy Loading demo
I'm running into a small issue right now when adding in options
which issue?
https://codepen.io/Matt-CopOffMatt/pen/poqZzjP
When the page loads, the first img is preloading without animating
that's good
I added in options to try and prevent this, to force it to be within view by setting threshold to 1.0
you fake it
you use loading="lazy"
if the image loads too quickly, you fake it
Why would it load though if it's outside of observer, which includes a threshold?
I might be misunderstanding the mozilla doc
The first one is not outside of the root. The root is the viewport. Maybe you are assuming incorrectly that the root is the parent?
From my understanding, my code removes/adds class based on the intersection of child cards returned from the
.cards
parent
Why would it ignore the first card if it should function the same across all child cards?In what way is it ignoring the first card?
I see the exact same behavior on all of them.
images load no matter what
Also, I just noticed. I passed options parameters here:
Should options go here?
const observer = new IntersectionObserver(entries, options => {
The first one is already intersecting the viewport on page load.
How so, if there's an element taking up 100VH?
You didn't put that in your codepen version.
or is that element not truely taking up the full viewport height ?
You mentioned before that you have a 100vh hero, but it's not in your codepen.
Updated, I apologized I missed it when copying
And it behaves correctly after you added that.
Here's what I see
It fades in for me.
Maybe you didn't reload the page. It only fades in once.
Hmm I think it has to do with the 100VH
I set main-hero to 110VH and it worked
use 100dvh
not 100vh
Ahh okay
Try chaning it back to 100vh but ensure that you scroll back to the top and reload.
Just curious, if I were to keep 100VH is there a way to solve this with options?
I have been, I don't think it recognizes true 100VH
It should work with 100vh. It worked in my browser with your code before you changed it to 110vh. But it only works once until the page is reloaded.
100dvh didnt work either
Is this something that can be solved with options?
define "it"
You can try changing the root margin, but it shouldn't be necessary.
what do you want from it?
Did you use Firefox? I just noticed that it works correctly in Chrome but doesn't work correctly in Firefox.
First child should load similarly to the other three.
Yeah
I just realized it's not the browser. There is something about the window size. I am investigating.
he has a
display: none
set the opacity to 0
remove the class
and use a transition
also, use blur tooI was going to eventually add in img src instead of display: none but I'm just confused why it wouldn't work the same as it does for 3/4 of the images
I determined that it stops working correctly if the window height is small. This might actually be a codepen issue. I am doing a test without Codepen to verify.
I'm experiencing it outside of codepen
Add a 1px margin-bottom to the hero.
I confirmed in multiple tests that adding 1px margin-bottom to the hero fixes it on all screen sizes.
instead of 1px margin, set it to 101dhv and 101vh
Yeah that's the same as changing the height
I probably wont run into this on something beside this demo
Yes, but it is changing by the minimum amount needed to fix it.
or, add some padding to the 2nd div, with the images
Am I passing in options incorrectly? Would something like this fix the issue?
options doesn't seem to be defining so I think im doing something wrong
are you padding an object to the intersection observer?
no, you arent
Intersection Observer API - Web APIs | MDN
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
That's where I'm reading
I thought it added a margin around the intersection screen size, where it would act similarly to adding margin to say the images for example
The options is in the wrong place. You made that the second parameter of the callback. It's supposed to be the second parameter of the InterSectionObserver.
Ahh okay thank you
Doesn't work as intented maybe im misunderstanding mozilla
Root Margin: This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections.
Shouldn't this act as adding a margin around the "observer" view? Aka, the viewport size?no
just makes it "bigger" or "smaller" for calculations
the element, not the viewport
I just noticed some problems with your code. I don't know if you now have something different from what is on Codepen, but the codepen version puts the options in the wrong place. Also, you don't actually need a root margin. You need to change the threshold to something like 0.2. Right now, the threshold isn't being used because the options are in the wrong place.
I just updated that actually. I was trying to figure out where to put that callback
you are observing if an element intersects the viewport, not if the viewport intersects an element
Sorry im still relatively new to JS
Ahh makes sense nows, that's where it's suppose to go
Basically
const observer = new IntersectionObserver(callback => {} options)
The options isn't a callback.
Okay true that also makes sense
callback, options*
I apologize
yes, that's why i sent the documentation
which explains this already
That's what I've been reading
Just get confused with some of these explanations
it is a bit convoluted, but that part is always clearly explained
it is the only part that's clear in it
what you had before was a callback that took 2 arguments
This was the explanation I sent here
instead of passing a callback and a 2nd object
made me believe that it adds margin to the observer. Therefore, my logic was that adding 1em, for example, would allow for a 1em gap before recognizing an observation
rait
wait
"root element"
that's the same as
:root
?
no, it isn't
it's for the root
optionWhich should be the card (container), no ?
no
root The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.
I thought so because they defined the root as the id scrollArea. I thought in my code example, the cards would be considered the scroll area since thats what's being observed
viewport makes more sense was just confused why theyre using an element over viewport
yes, but you aren't observing
.cards
but each .card
I'll need to read more about this api to fully understand
this is my first attempt at trying to use it so im still trying to understand
I appreciate both of your help π thank you@ChooKing@αΌΟΟΟ
you're welcome
If you are not apposed to using a library, GSAP.ScrollTrigger is amazing to leverage IO.
i.e. (old but still good example) https://codepen.io/b1mind/pen/eYJWqey
Tbh, I need to stick to strictly JS right now
just need to get a grip been trying to learn consistently for around 1 month now
yup I get that just figured I'd mention it.
This is very cool btw
Thanks that was my first time using it, was for a codepen challenge the month they released it. (I had used a scrollMagic lib and IO before though so had some exp in trigger animations)
https://codepen.io/b1mind/pen/bGgEEqJ So is this π
hah yea... I still need to make that work infinite scrolling. Thanks!