Jonas: All right.
So let's now implement the same sticky navigation
that we implemented in the last video,
but this time, using the new intersection observer API.
But what actually is the intersection observer API,
and why is it so helpful?
Well, this API allows our code to basically
observe changes to the way that a certain target element
intersects another element, or the way
it intersects the viewport.
And so from this definition alone,
I think you can see that this will actually
be useful in implementing our sticky navigation.
But let's actually start this video by learning
how the intersection observer API actually works,
but without our sticky navigation,
because at the beginning, this can seem
a bit intimidating and confusing, okay?
So to use the intersection observer API,
we need to start by creating a new intersection observer.
So that's IntersectionObserver, just like this,
and then here, we will have to pass in a callback function
and an object of options.
But let's leave that for a bit later, okay?
So let's first store the result of calling this function
into a new variable.
So let's simply call it the observer,
and so now, we have to use this observer
to basically observe a certain target.
So observer and then the method we call on that is observe.
And then here, the target element.
And this one will be section one,
and so this is an element that we already selected before.
So this will make a bit more sense
once we actually create the callback and the options here,
and let's start by the options.
So const, let's call it the observer options,
so obsOptions,
so this is gonna be an object,
and we can also already create the callback function.
Let's simply call this observerCallback.
All right, and of course, we could've created
these right here, because this is where we will now
pass them in, so first the callback,
and second the options, okay?
And so once again, we could've written the function
and the object directly in here,
but I think it's a bit cleaner to have them separately.
So let's now start with the options here,
and so this object needs first a root property.
And this root is the element
that the target is intersecting.
So again, this here is the target,
and the root element will be the element
that we want our target element to intersect.
And again, this will all make more sense
once you see this in action.
So we could now here select an element
or as an alternative, we can write null,
and then we will be able to observe our target element
intersecting the entire viewport, all right?.
So basically, this entire rectangle here,
which shows the current portion of the page, okay?
And then second, we can define a threshold.
Threshold, and this is basically the percentage
of intersection at which
the observer callback will be called,
so this callback here.
So again that's very confusing,
let's just set it to 10%, which is 0.1,
and then when we have this callback function here
also created, then we will see what actually
happens here in practice.
All right.
So this callback function here will get called
each time that the observed element,
so our target element here, is intersecting
the root element at the threshold that we defined, okay?
So take note of this because this is actually
a bit hard to figure out from reading the documentation.
And so it's a good idea to keep note
of what I'm saying here.
So in the current example, whenever the first section,
so our target here, is intersecting the viewport at 10%,
so the viewport, because that's the root,
and 10% because that's the threshold.
So whenever that happens, then this function here
will get called and that's no matter if we are scrolling
up or down, all right?
And this function will get called with two arguments,
and so we can specify them here,
and that's the entries and the observer object itself.
All right, so this object here basically
will also get passed into the callback function, all right?
Now this case, we're only interested in the entries,
but sometimes using the observer is also useful.
Now we can have actually multiple thresholds,
so here we can have an array, and I will do that
in a minute, and so these entries here
are actually an array of the threshold entries, okay,
and so in this case again, there's only one element there,
but let's create a more general function already
which basically loops over these entries
so that we can take a look at all of them.
And so let's just do that, so basically,
simply log them to the console,
and that will already be good enough for us
to take a look at how this all works.
So entry, and so you see I'm using an arrow function here,
but of course any other function would work just the same.
Let's give it a save now, and you see that already,
we get an intersection observer entry,
and so that is this entry here.
And so this is not really interesting yet,
because this is just the first one that we got
and we see that the ratio here is at zero,
but let's see in a second what that actually means.
So I will start scrolling here, and you see
that now we actually got our first real entry here,
which appeared here because our target element
came into the viewport.
So our target element is this whole h1
that's down here, and so you see that it started
intersecting the viewport, okay?
And so our observer is observing that,
and so now here you see that the intersection ratio
at the time that this callback here was called,
so this one here, was at 0.10, and then this.
And so that's exactly the 10% threshold
that we defined over here, all right?
We also get this isIntersecting property,
which in this case is true, and that is because indeed,
our target, so again this whole section here,
is now intersecting the viewport.
And we are looking for the viewport, remember,
because we set the root to null.
All right?
So we also get the target itself here,
and a couple of other stuff,
but the intersection ratio is actually
the most interesting one and also the isIntersecting here.
Okay, so let's scroll down a little bit more,
and let's close this actually,
and let's scroll up again, and so again,
we get a new entry here, and so again,
it should be close to 10%,
but now it happened as we were moving up, right?
And so again it was at 10%, but now,
it is no longer intersecting, okay?
And it's not intersecting because the threshold is at 10.
And so now less than 10% basically of our target here
are inside of the root, so inside of the viewport.
All right, and so you can start to see
why this is more efficient.
So it's because we only get this kind of event here
in the situation that we're actually interested in.
So in this case, that's this threshold of 10%.
Okay, and as we keep scrolling here,
now it will come back again,
and now it is again intersecting,
let's keep scrolling, and all of this here is section one,
and now we got another one.
So let's see, and so now it is back to not intersecting,
because now we no longer have 10% visible, okay?
So you can think of this threshold here
at the percentage that we want to have visible in our root.
So in our viewport in this case, and so,
right now we are back to having less than 10%,
and so it is no longer intersecting here.
And again, if we scroll up a little bit more,
then we get another event here because now
we are back to having 10% intersection ratio, okay?
So this is the very fundamentals of how
the intersection observer API works,
and I confess that it is a bit confusing
and it took me quite some time to figure all of this out,
and so probably the best idea is for you
to stop this video now and explore this
a little bit on your own.
So you can use different threshold values,
and maybe use different targets here,
you can even use different roots,
and so it would be a good idea to experiment
a little bit more with this.
Now what I'm gonna do here is to now specify an array,
so to specify different thresholds,
and one of them is gonna be zero,
and the other one 0.2, so that's 20%.
So 0% here means that basically our callback
will trigger each time that the target element
moves completely out of the view,
and also as soon as it enters the view, okay?
And so that's because the callback function
will be called when the threshold is passed
when moving into the view and when moving out of the view,
and this is really important to remember here.
On the other hand, if we specified one here,
like this, then that means that the callback
will only be called when 100% of the target
is actually visible in the viewport.
So in the case of this section one,
that would be impossible because the section itself
is already bigger than the viewport.
But let's now experiment with zero here,
and so right away, we again get this event here,
and you see that the intersection ratio
is exactly at zero, but it is already intersecting,
and so it's because basically the threshold here
was already passed.
All right?
So I think another one happened there,
let's just reload, oh, and that was probably
from our 20%.
Yep, so you see, this is the 20 threshold,
the one that we set here now,
and so this one now happened a little bit later than before,
because before, it was happening at 10%,
but now only at 20%.
So that's a bit later.
Let's just clear this and keep scrolling,
and so now this section is almost scrolling out of view,
and you see, we now get this 20% here.
So a bit less than 20%, and as we keep scrolling,
then, then as this line hits here the top of the viewport,
we should get another one, which is when
this zero here is again reached.
So let's try that.
And indeed, here it is.
So we are back to intersection ratio at zero,
and the element is no longer visible, all right?
So this is how the intersection observer API works,
and let's now quickly use this in order to implement
our sticky navigation, because at this point,
I'm sure that you can already imagine
how this can be very useful for implementing this
in an easy way.
So, let's think about this.
When do we want our navigation to become sticky?
Well, we want that to happen essentially
when the header moves completely out of view.
So basically, all of this part here,
which is the header, when we can no longer see it,
that's when we want then to display the navigation, okay?
And so this time, we are going
to observe the header element.
So let's start by selecting that.
And it is an element with the class of header, all right?
Let's actually get rid of this part here.
Okay, and now let's create our observer.
So this time I'm calling it the headerObserver,
just so we know exactly what is being observed.
So new IntersectionObserver,
and then I'm gonna pass in the arguments later,
and then I will use the headerObserver
to observe the header.
Okay?
So this all kind of sound like normal English sentences
if we read it like this, right?
So here we again need a callback function,
so let's just call this stickyNav,
actually, and then again the options.
Now this time, I will just create
the options object right here, all right?
And so the root will once again be null,
because we are again interested in the entire viewport,
and then the threshold.
So, I said that we are interested in showing
the sticky navigation as soon as this header
scrolls completely out of view.
So what threshold does that mean?
Well, that's just zero, right?
So when 0% of the header here is visible,
then we want something to happen.
Okay, and now let's define this function here,
and here we will have exactly the functionality
that we want to happen, so to add and to remove
the sticky class.
So this function gets the entries, and this time,
I'm not gonna specify the observer,
because in this case, we don't need it.
Also, there's only one threshold here,
so I don't need to loop over the entries.
We can simply get the first one,
and let's actually use destructuring
to get the first element, so entry,
to get the first element out of entries.
And remember that this is the same
as writing entries zero, right?
So, let's just start by taking a look at this entry,
just to see if it works.
And here, one more time, we already get something,
so I'm not sure why that is actually,
because this has nothing to do with our threshold.
All right, but let's just wait for the next one,
and so it should be appearing soon,
because our target, so this header is almost moving
out of the view, and indeed, here it is.
So intersection ratio is zero,
and it is no longer intersecting.
And so now, let's actually create the logic
of adding and removing the classes.
So basically what we want to do
is to first add it here, and so let's start with that.
But you see that now it actually already applied this class,
and that's because right now, our target element
is intersecting the root, so the viewport, all right?
So we will use that in a second.
Let's just see what happens next.
So here we will now get our next one, right?
And so now it is no longer intersecting.
So intersection ratio zero, but no longer intersecting.
And so this is actually the condition
that we are interested in, right?
So only when the header is not intersecting the viewport
is when we want to add that sticky class,
because watch what happens when we move back up.
So right now, you see we got this new entry here,
and so in this case, it is again intersecting,
and so, and in this case where we are scrolling up
is when we actually want to remove that sticky class, right?
So we are entering the header again,
and so that's the point in time
where we want the sticky class to be gone.
And so now we can use this intersecting property
to do exactly that.
So we can say that if the entry.isIntersecting is false,
basically, then do this.
Right?
So again, when the target is not intersecting the root,
then we want the sticky class to be applied.
And else, we want to remove it.
So let's reload,
and now indeed, the sticky class is no longer there, okay?
And now watch what happens as this line
reaches the top there, then yeah, here it is.
So there is our sticky navigation,
so that's great, and indeed, it is because
it is now no longer intersecting.
And so now the final test is when we move back up,
then it is gone, and so as we scroll around this point,
it will basically add and remove the class
according to the scrolling direction.
Now we're actually not really done yet
because I want to make this a little bit different,
or a bit better.
So let me show you the demo here,
so just to make sure, I'm gonna reload it here,
never mind this effect you just saw here.
But watch when the navigation comes in here.
So you see when that happens, it's basically
when the distance between the start of the section
and the viewport here is just the same
as the navigation.
You see that?
And so like this, the navigation doesn't overlap
this section here right in the beginning.
So here the navigation comes exactly before
the first section starts.
And so let's implement it here as well
and thankfully, that's pretty easy to do,
and we can do it by specifying another property here
which is the rootMargin, okay?
And this root margin here, for example 90,
is a box of 90 pixels that will be applied
outside of our target element,
so of our header here, okay?
And so now it is as if the header
did not stop right here, but instead, out here.
All right?
So as if the header went all the way here.
Now we actually want the header to basically stop here.
And so that's -90.
Okay, and here we then need to specify also the unit,
which has got to be pixels, so unfortunately,
percentage does not work, and also rem does not work
so it has to be pixels, and so that's why
I'm using 90 pixels here.
And 90 is the height of the navigation,
and so let's test this here again.
So let's see, and so let's wait for it,
and did you see that?
Indeed, now the navigation appeared exactly 90 pixels
before the threshold was actually reached.
So you see, now we get the event here even later,
and all the data here is still the same.
This is simply a visual margin that gets applied.
So you can play around with this as well
with the difference between a positive
and a negative margin to figure out really how this works,
because this root margin here is very important
to specify correctly, as we need it in many situations.
Now just as one final detail, let's actually calculate
this height here dynamically.
And for that, we can use the getBoundingClientRect function,
because for example if you have a responsive site,
then probably the size of all your elements
will change at certain points, and then it's not a good idea
to have this 90 pixels here hard coded
right there in the code.
Let's just take a look so we see
what we actually need to get.
So this is the rectangle, and so here we actually
have a height property.
So this is where that 90 comes from.
So let's read that directly here, height.
All right, and so now here we can use that.
Let's create a nice template string here,
navHeight, and with that, I believe we're actually done.
All right, and of course this works
no matter how large or how small the viewport is.
Okay, now it works beautifully and correctly
in all situations and this is a lot more performant
and a lot better than the solution that I showed you
in the previous lecture.
So play around with this but we will actually
also have I think two more examples
with the intersection observer API coming up
and so that's gonna be another great opportunity
to learn how this API really works,
because it's quite important actually to do things
on certain positions on the page,
so things related to scrolling.