N
Nuxt2mo ago
yurian

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.
2 Replies
kapa.ai
kapa.ai2mo ago
Hi @yurian. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the information provided in the knowledge sources, I can offer some insights and potential solutions to your scrollspy issue with Nuxt. 1. Preventing scroll jumps: The scroll jumps you're experiencing are likely related to the default scroll behavior of Nuxt's router. To address this, you can customize the scrollBehavior in your router options. You can create a file ~/app/router.options.ts (if it doesn't exist already) and add a custom scrollBehavior function. Here's an example that might help:
export default {
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
top: 50 // Adjust this value as needed
}
}
}
}
export default {
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
top: 50 // Adjust this value as needed
}
}
}
}
This custom scrollBehavior will smoothly scroll to the target element when a hash is present in the URL, and it adds a small offset to prevent the element from being at the very top of the viewport Docs > Guide > Recipes > Custom Routing > Router Options > Scroll Behavior for hash links (https://nuxt.com/docs/guide/recipes/custom-routing#scroll-behavior-for-hash-links). 2. Smooth scrolling: To enable smooth scrolling globally, you can add the following CSS:
html {
scroll-behavior: smooth;
}
html {
scroll-behavior: smooth;
}
This will make all scrolling smoother, including when navigating to hash links Stack Overflow Post > How to prevent the scroll from jumping to the top of the web page on Nuxt3 reload (https://stackoverflow.com/questions/76810996). 3. Considering scroll-margin-top: Recent updates to Nuxt have improved how it handles scrolling to elements with scroll-margin-top set. This might help with your scrollspy implementation Github Pull Request #9187 (https://github.com/nuxt/nuxt/pull/9187). 4. Collecting elements from nested components: Regarding your issue with collecting elements only from the current component and not their children, you might need to adjust your process function to recursively search through all child elements. However, this is not directly addressed in the provided knowledge sources, so I don't have specific Nuxt-related advice for this part of your question. Remember that these solutions are based on the latest available information about Nuxt 3. If you're using a different version, some details might vary. If these suggestions don't fully resolve your issue, you might want to consider creating a minimal reproduction of the problem and sharing it on the Nuxt GitHub repository for more specific assistance. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@289192229921095681> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Want results from more Discord servers?
Add your server