yurian
yurian
NNuxt
Created by yurian on 10/23/2024 in #❓・help
Scrollspy is making goofy jumps
Hello there. I'm working on a scrollspy feature for my website. To change the hash, I'm using a simple navigateTo call, but I've tried with router.push({hash: newHash}) and I get the same results, which is these goofy jumps that are shown in the video. Is there a way to make those navigations without the scroll going to to the top of the element the hash refers to? This is my code:
<script lang="ts" setup>
const scrollspyContainer = ref<HTMLElement | null> (null)
const targets = ref<HTMLElement[]>([])

function isHTMLElement(element: Element): element is HTMLElement {
return 'addEventListener' in element
}

function docOffsetTop(el: HTMLElement): number {
const elRect = el.getBoundingClientRect()
return elRect.top + window.scrollY
}

function process(container: Element): HTMLElement[] {
const processImpl = (c: Element, acc: HTMLElement[] = []): HTMLElement[] => {
if (isHTMLElement(c) && c.id !== '') {
return acc.concat([c], Array.from(c.children).flatMap(child => processImpl(child, acc)))
} else {
return acc
}
}

return processImpl(container)
.sort((a, b) => docOffsetTop(a) - docOffsetTop(b)) // Sorts it in order of absolute height from the top of the document.
.filter(c => c !== container)
.map((c) => { c.classList.add('border', 'border-red-300'); return c })
}

onMounted(() => {
if (scrollspyContainer.value) {
targets.value = process(scrollspyContainer.value)
}
})

const { y } = useWindowScroll()

watchThrottled(
y,
() => {
const first = R.pipe(
targets.value,
R.dropWhile((el) => {
const elComputedStyles = window.getComputedStyle(el)
const marginTop = Number.parseFloat(elComputedStyles.scrollMarginTop) || 0
const rect = el.getBoundingClientRect()

return rect.bottom < marginTop
}),
R.first(),
)

if (first) {
const firstHash = `#${first.id}`
if (firstHash !== hash.value)
navigateTo({ hash: firstHash })
} else {
navigateTo({ hash: '' })
}
},
{ throttle: 8.33 /* This is 120 frames per second. */ },
)
</script>
<script lang="ts" setup>
const scrollspyContainer = ref<HTMLElement | null> (null)
const targets = ref<HTMLElement[]>([])

function isHTMLElement(element: Element): element is HTMLElement {
return 'addEventListener' in element
}

function docOffsetTop(el: HTMLElement): number {
const elRect = el.getBoundingClientRect()
return elRect.top + window.scrollY
}

function process(container: Element): HTMLElement[] {
const processImpl = (c: Element, acc: HTMLElement[] = []): HTMLElement[] => {
if (isHTMLElement(c) && c.id !== '') {
return acc.concat([c], Array.from(c.children).flatMap(child => processImpl(child, acc)))
} else {
return acc
}
}

return processImpl(container)
.sort((a, b) => docOffsetTop(a) - docOffsetTop(b)) // Sorts it in order of absolute height from the top of the document.
.filter(c => c !== container)
.map((c) => { c.classList.add('border', 'border-red-300'); return c })
}

onMounted(() => {
if (scrollspyContainer.value) {
targets.value = process(scrollspyContainer.value)
}
})

const { y } = useWindowScroll()

watchThrottled(
y,
() => {
const first = R.pipe(
targets.value,
R.dropWhile((el) => {
const elComputedStyles = window.getComputedStyle(el)
const marginTop = Number.parseFloat(elComputedStyles.scrollMarginTop) || 0
const rect = el.getBoundingClientRect()

return rect.bottom < marginTop
}),
R.first(),
)

if (first) {
const firstHash = `#${first.id}`
if (firstHash !== hash.value)
navigateTo({ hash: firstHash })
} else {
navigateTo({ hash: '' })
}
},
{ throttle: 8.33 /* This is 120 frames per second. */ },
)
</script>
Also, when I collect the elements to be hashed on the route, it seems is only collecting those at the current component and not their children. Is there a workaround this? Thanks a lot for reading this. Any help is really appreciated.
5 replies