i think this is a problem of scope inside my helper function, i have successfully implementing the same logic in my component and it works perfectly
i have already using a composable function to use ref() with the variable that i want to return
=> this is the working code inside my component
// component.vue
setup() {
const subnavDiv = ref(false) // this is the variable that the bellow code update
onMounted(() => {
const routEl = document.querySelector('.main')
const fixedNav = document.querySelector('.main-nav')
const el = document.querySelector('.search')
let prevYPosition = 0
let directionVar = 'up'
const options = {
root: routEl,
rootMargin: `${fixedNav.offsetHeight * -1}px`,
threshold: 0.7
}
const setScrollDirection = () => {
if (routEl.scrollTop > prevYPosition) {
directionVar = 'down'
} else {
directionVar = 'up'
}
prevYPosition = routEl.scrollTop
}
const onIntersection = ([e]) => {
setScrollDirection()
if ((directionVar === 'down' && e.boundingClientRect.top > 0) || !e.isIntersecting) {
console.log('down')
subnavDiv.value = true
} else {
subnavDiv.value = false
}
if (
e.isIntersecting &&
(e.boundingClientRect.top < fixedNav.offsetHeight ||
e.boundingClientRect.top > fixedNav.offsetHeight)
) {
subnavDiv.value = false
}
}
const observer = new IntersectionObserver(onIntersection, options)
observer.observe(el)
})
return {
subnavDiv
}
}
}
}
=> after moving the same code out of the component
// component.vue
setup() {
const subnavDiv = ref(false)
onMounted(() => {
const rootEl = document.querySelector('.main')
const fixedNav = document.querySelector('.main-nav')
const el = document.querySelector('.search')
subnavDiv.value = onIntersect(rootEl, 0.7, fixedNav, el) // the helper function call: this line is supposed to update the value of subnavDiv
})
})
/////////////////////// $$$$$$$$$$$$$$$$$$ ///////////////
onIntersect.js // the helper function
const onIntersect = (rootElement, thresholdValue, elementToChange, elementToWatch) => {
let prevYPosition = 0
let directionVar = 'up'
const options = {
root: rootElement,
rootMargin: `${elementToChange.offsetHeight * -1}px`,
threshold: thresholdValue
}
let bool = false // this is the variable that i am trying to return from this function
const setScrollDirection = () => {
if (rootElement.scrollTop > prevYPosition) {
directionVar = 'down'
} else {
directionVar = 'up'
}
prevYPosition = rootElement.scrollTop
}
const onIntersection = ([e]) => {
setScrollDirection()
if ((directionVar === 'down' && e.boundingClientRect.top > 0) || !e.isIntersecting) {
console.log('down')
bool = true
} else {
bool = false
}
if (
e.isIntersecting &&
(e.boundingClientRect.top < elementToChange.offsetHeight ||
e.boundingClientRect.top > elementToChange.offsetHeight)
) {
bool = false
}
}
const observer = new IntersectionObserver(onIntersection, options)
observer.observe(elementToWatch)
return bool // the return (not returning any thing)
}
export default onIntersect
The important part is the ref, the purpose of the ref pattern is to pass a value by reference between scopes. onIntersect
is supposed to be a composable and follow regular conventions for them. It's supposed to be called in setup
rather than lifecycle hooks.
Direct DOM access is not desirable in Vue. If main
, etc are created in the same components, they should be template refs, otherwise they can be provided to the composable as refs, so it could make use of reactivity. The use of onMounted
inside a composable would make it inflexible and prone to race conditions. Considering that elementToWatch
needs to be accessed first, it could be:
const useIntersect = (rootElRef, threshold, changedElRef, watchedElRef) => {
const bool = ref(false);
let observer;
watchEffect(() => {
const rootEl = unref(rootElRef);
const changedEl = unref(changedElRef)
const watchedEl = unref(watchedElRef);
if (rootEl && watchedEl) {
...
observer = new IntersectionObserver(onIntersection, options)
observer.observe(watchedEl)
}
return () => {
// clean up
observer?.disconnect();
}
});
return bool;
};
DOM elements can still be accessed directly if necessary, but they are refs any way:
...
const rootElRef = ref(null);
const isSubnav = useIntersect(rootElRef, ...);
onMounted(() => {
rootElRef.value = document.querySelector('.main');
...
As a side effect, it can react to the changes in parameters, e.g. it can be forced to recreate an observer if the options need to be changed.