The nuxt-anchorscroll
package
To begin with, the original problem and its solution have been described already. So here we will talk about what this solution became later - module if be more accurate.
As you may already note, my blog site have #
symbol at the end of headings (1-4 for now).
Which scrolls to anchor element and allows to skip unnecessary parts at page loading.
But this functionality is little bit tricky...
The two layouts types
#First of all, there is only two ways to layout entire page:
- The inheriting of
html
element and extending it when needed. - The fixing of
html
element, cropping it and extending it child when needed.
In the first case we can also put some minimal size for having bottom footer and centered items, when its total size is much less then available on client page which depends on window size.
The standard layout
#In first order, the standard layout is the historical way to layout the html pages. The only thing, that is required for its implementation, is to pass size directly to the target element.
html {
// Minimal sizes sets on html page to center item when their size is small
min-height: 100%;
min-width: 100%;
body {
// Allowing body to have 100% of parent sizes
height: 100%;
width: 100%;
margin: 0;
div#__nuxt {
// Allowing target to have 100% of parent sizes
height: 100%;
width: 100%;
display: flex;
}
}
}
If you already using that kind of layout (and mostly are) with anchor elements, then you may note the instant animation on click like showed under.
But there is the some problems:
- The
html
tag requiresscroll-behavior: smooth
in case when you want to have fine looking scroll to anchor. - The possible offset (like in this blog) is not respected.
The first one solution will affect entire page which can be undesirable. But the second one can be solved tricky via hidden shifted elements.
Sounds overcomplicated, isn't?
So, the solution provided by module is to scroll over surfaces (html and body by default). This allows to not make any changes in current layouts and behavior except modification for anchor link.
The thing is, browser handles scroll to anchor when we use standard layout. And we need to trick it with the false anchor name: the fake but user friendly hash in url and its mangled version for the real anchor element.
The nuxt-anchorscroll
provides ability to setup application in app.vue
:
import { toValue, useNuxtApp } from '#imports'
const nuxtApp = useNuxtApp()
// Set custom anchor for Y axis with dynamic header support
nuxtApp.$anchorScroll!.defaults.toAnchor = () => ({
behavior: 'smooth',
offsetTop: -(toValue(useNuxtApp().$headerHeight) ?? 0) * 1.2,
})
// Add route specialization for fixed solution
// See docs for explanation
nuxtApp.$anchorScroll!.matched.push(({ path, hash }) => {
// The undefined value means: try another match
if (!hash)
return undefined
// All anchor elements on website must be mangled
// It's possible to sync prefixes \ postfixes on different routes if needed
const targetSelector = `#real-${hash.slice(1)}`
const targetElement = document.querySelector(targetSelector)
// When no anchor found - try another match
if (!targetElement)
return undefined
return {
toAnchor: {
target: targetElement as HTMLElement,
// Using default anchor setting which can be dynamic by design
scrollOptions: toValue(
useNuxtApp().$anchorScroll?.defaults?.toAnchor ?? {}
),
},
}
})
app.vue
And then an anchor element just used mangled id:
<script setup lang="ts">
import { useAnchorScroll } from '#imports'
const { id } = defineProps<{ id: string }>()
// scrollToAnchor uses default anchor settings and can be used
// for smooth scrolling
const { scrollToAnchor } = useAnchorScroll()
const fixedId = `real-${id}`
</script>
<template>
<div>
<div :id="fixedId">
<slot />
</div>
<NuxtLink
:href="`#${id}`"
@click="scrollToAnchor(fixedId)"
>
#
</NuxtLink>
</div>
</template>
component.vue
And as a result we can see the next:
The preferred layout
#In contrast to the first layout kind, this one relays on cropped parent tag,
which usually is html
.
html {
// Fix html sizes and crop all
height: 100vh;
width: 100vw;
overflow: hidden;
// Body is used as surface
body {
// Inherit parent sizes and disable crop for desired axis
height: 100%;
width: 100%;
margin: 0;
overflow-y: scroll;
overflow-x: hidden;
div#__nuxt {
// Target element must have 100% as minimum size for keeping
// the elements on the page center when needed
min-width: 100%;
min-height: 100%;
display: flex;
}
}
}
And the only thing that we need to setup is the header size if needed:
import { toValue, useNuxtApp } from '#imports'
const nuxtApp = useNuxtApp()
useNuxtApp().$anchorScroll!.defaults.toAnchor = () => ({
behavior: 'smooth',
// headerHeight + 20%
offsetTop: -toValue(useNuxtApp().$headerHeight ?? 0) * 1.2,
})
app.vue
And that's all you need! Easy as pie!
Playground
#In case if you want to explore the live example, you can try playground or checkout my blog source code.
Quick setup
#- Add
nuxt-anchorscroll
dependency to your project
You can use your favorite package manager (I prefer yarn)yarn add -D nuxt-anchorscroll pnpm add -D nuxt-anchorscroll npm install --save-dev nuxt-anchorscroll
- Add
nuxt-anchorscroll
to themodules
section ofnuxt.config.ts
export default defineNuxtConfig({ modules: [ 'nuxt-anchorscroll', ] })
- Additionally, if you are using transitions, probably you also want to scroll on different hook
export default defineNuxtConfig({ modules: [ 'nuxt-anchorscroll', ], anchorscroll: { hooks: [ // Or any valid hook if needed // Default is `page:finish` 'page:transition:finish', ], }, })