'In Vuejs3 how to make the sticky header component change background colour when intersecting with other components?
I am just wondering, how to structure my code to make my sticky <Header />
component change its background-color
when intersecting with other components (in this case with <Hero />
and <Footer />
.
To make things more complicated, I use vue-router
.
Link to my CodeSandbox
The structure is as follows:
App.vue
<template>
<ul class="nav">
<li>
<router-link to="/">Home</router-link>
</li>
<li>
<router-link to="/about">About</router-link>
</li>
</ul>
<Header />
<router-view />
</template>
views/Home.vue
<template>
<Child1 />
<Child2 />
<Hero />
<Child3 />
<Footer />
</template>
<script>
import Hero from "@/components/Hero";
import Child1 from "@/components/Child1";
import Child2 from "@/components/Child2";
import Child3 from "@/components/Child3";
import Footer from "@/components/Footer";
export default {
components: {
Hero,
Child1,
Child2,
Child3,
Footer,
},
};
</script>
When I follow some tutorials on yt, the authors use the IntersectionObserver
, so I wanted to give it a try:
Header.vue
<template>
<section
class="header"
>
<div class="header__title"
:class="{
header__scroll: isContrastActive,
}"
>
I am an awesome header that is changing it's background colour <br />
when intersecting with other components
</div>
</section>
</template>
<script>
export default {
data() {
return {
isContrastActive: false,
observer: null
};
},
methods: {
activateObserver() {
this.observer = new IntersectionObserver(
([entry]) => {
console.log("isIntersecting: ", entry.isIntersecting);
if (!entry.isIntersecting) {
this.isContrastActive = true;
} else {
this.isContrastActive = false;
}
},
{ rootMargin: "-5% 0px 0px 0px" }
);
document
.querySelectorAll(".observer")
.forEach((el) => this.observer.observe(el));
},
},
mounted() {
this.activateObserver();
},
};
</script>
<style scoped>
.header {
position: sticky;
top: 0;
width: 100vw;
height: auto;
z-index: 10;
}
.header__title {
background-color: yellow;
color: black;
padding: 8px 4px;
text-align: center;
}
.header__scroll {
background-color: orange;
}
</style>
Also added the class="observer"
to the <Hero />
and <Footer />
components.
But it doesn't seem to work.
I have read, that in vue the use of querySelector
is considered to be an antipattern.
If this is the case then how I could structure my code to get what I want?
Thanks in advance
EDIT1:
I noticed, that if I change the text in the <Header />
component and save then everything seems to work just fine.
But on page refresh, it is still not working.
Think that the querySelectorAll
could be the reason.
SOLUTION 1: The most obvious solution is of course:
mounted() {
setTimeout(() => {
this.activateObserver()
}, 500)
},
but it ain't an elegant solution :/.
Solution 1:[1]
The code you're using makes the assumption all observed elements are already in DOM.
You need to make the observable available to all your pages (use the preferred state management solution).
Your activateObserver
should become:
createObserver() {
someStore().observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) {
this.isContrastActive = true;
} else {
this.isContrastActive = false;
}
},
{ rootMargin: "-5% 0px 0px 0px" }
);
}
Then, in any page which has observed elements, use template refs to get all elements you want observed.
In onMounted
you start observing them, in onBeforeUnmount
you stop observing them.
const observed = ref([]);
onMounted(() => {
observed.value.forEach(someStore().observer.observe)
})
onBeforeUnmount(() => {
observed.value.forEach(someStore().observer.unobserve)
})
How you select your elements using template refs is irrelevant for this answer. The point is you need to start observing the DOM elements referenced after the component has been mounted and unobserve them before unmount.
Note: in theory, it is possible that any of your views is rendered before the observer
has been added to the store. (In practice it's a rare case, so you probably don't need this).
However, if that's the case, you want to create a watcher for the observer
in your pages and start observing only after you have the observer, which might be after the page has been mounted.
The principle is simple: you need both observer and observed for this to work. observer
is available after it is added to store, observed
are available after onMounted
until onBeforeUnmount
in any of the pages containing them.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |