diff --git a/layouts/default.vue b/layouts/default.vue index 22dc1982..8d9fcb08 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -16,6 +16,11 @@ export default Vue.extend({ head(): Required> { return this.$nuxtI18nHead({ addSeoAttributes: true }); }, + watch: { + $route(): void { + this.$scroll(); + }, + }, mounted(): void { // Provide calls to this.$gtag here to update Analytics }, diff --git a/nuxt.config.js b/nuxt.config.js index c5d2d5b6..cf7b247d 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -33,6 +33,7 @@ export default { // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins 'plugins': [ { src: '~/plugins/google-gtag.client.ts', mode: 'client' }, + { src: '~/plugins/scroll.client.ts', mode: 'client' }, ], // Auto import components: https://go.nuxtjs.dev/config-components diff --git a/plugins/scroll.client.ts b/plugins/scroll.client.ts new file mode 100644 index 00000000..e62f2e7b --- /dev/null +++ b/plugins/scroll.client.ts @@ -0,0 +1,40 @@ +import { Plugin } from '@nuxt/types'; + +declare module 'vue/types/vue' { + interface Vue { + $scroll(): void; + } +} + +/** + * How does this work? A good question. It may be buggy. + * + * When user navigation occurs, history is written to the browser to be used by + * the back/forward buttons. This history maintains an immutable state for each + * page. We are referencing one of the values of that state stack, the key. In + * Chrome and Firefox, this /appears/ to be a monotonically increasing + * string-encoded number. Possibly the number of milliseconds since the + * page/website was first navigated to. More testing is required. + * + * We detect when the value on the page we're on is higher than the largest + * value we've seen before. If it is, it's a new page, and we scroll. If it's + * not, we rely on the user's browser settings to decide to scroll to their + * previous position or not, depending on preferences. + * + * This function is called universally by watching to see when $route changes + * in the layout for the pages. If it's broken, first confirm your page's + * layout does call this.$scroll() inside of watch on $route. + */ +const scrollPlugin: Plugin = (_, inject) => { + let historyKeyMax = 0; + + inject('scroll', () => { + const historyKey = +history.state.key; + if (historyKeyMax < historyKey) { + historyKeyMax = historyKey; + window.scrollTo(0, 0); + } + }); +}; + +export default scrollPlugin;