DEV Community

loading...

How to Locomotive Scroll and Nuxt (with example and gotchas)

didomarchet profile image Davide Marchet Updated on ・4 min read

(Original repo and files https://github.com/DidoMarchet/starter-kit-nuxt-locomotive-scroll)

❤️ Every one loves smooth scrolling!

💤 But sometimes working with Javascript frameworks and DOM can be boring and love fades away.

📦 With this simple starter kit you can have fun with Locomotive Scroll and Nuxt without giving it a second though.

Table of content:

You can try this starter kit by cloning this repo and running:

# install dependencies
$ npm install

# run dev enviroment
$ npm run dev

# generate static project
$ npm run generate
Enter fullscreen mode Exit fullscreen mode

Plugin

First of all we setup the plugin enabling Locomotive Scroll instance works globally both in our component and for your own purposes.

In /LocomotiveScroll/plugin/index.js we create the plugin:

import LocomotiveScroll from 'locomotive-scroll'
import 'locomotive-scroll/dist/locomotive-scroll.css'

const install = (Vue) => {
  Vue.prototype.LocomotiveScroll = LocomotiveScroll
}

export default install

if (typeof window !== 'undefined' && window.Vue) {
  window.Vue.use(install)
  if (install.installed) {
    install.installed = false
  }
}
Enter fullscreen mode Exit fullscreen mode

After the setup, it will be used in /plugins/client.js.

/plugins/client.js works with mode: 'client' in the Nuxt plugins configuration .

Component

This component is an useful wrap for our Locomotive Scroll implementation.

Below are the main steps of the implementation.

Complete code can be found here /LocomotiveScroll/component/index.js.

Markup

<div
  v-locomotive="{ options }"
  class="js-locomotive"
>
  <slot />
</div>
Enter fullscreen mode Exit fullscreen mode

The v-locomotive directive gets access to low-level DOM.

It takes one argument options.

options is a computed obtained merging the defaultOption data property with the gettedOptions prop.

defaultOption and gettedOptionscontain the Locomotive Scroll instance options.

computed: {
  options () {
    // this.defaultOptions = { smooth: true }
    // this.gettedOptions = { offset: ['30%',0], direction: 'horizontal' }
    return { ...this.defaultOptions, ...this.gettedOptions }
  }
}
Enter fullscreen mode Exit fullscreen mode

Through the slot element we're able to pass content to the component from each page.

Directive

directives: {
  locomotive: {
    inserted (el, binding, vnode) {
      vnode.context.locomotive = new vnode.context.LocomotiveScroll({ el, ...binding.value.options })
      vnode.context.locomotive.on('scroll', (e) => {
        vnode.context.onScroll(e)
        vnode.context.$emit('scroll')
      })
      vnode.context.$emit('init')
    },
    unbind (el, binding, vnode) {
      vnode.context.locomotive.destroy()
      vnode.context.locomotive = undefined
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the inserted hook we create the new instance of Locomotive Scroll from the plugin previously created and we assign it to locomotive data property.
The inserted hook guarantees the parent presence.

Once initialized we listen to scroll event.

Each time scroll event is fired we call onScroll method.

onScroll takes as parameter the scroll instance and uses this data to fill the store (/store/app.js) making the state of the scroll accessible and usable in all our application.

methods: {
  onScroll (e) {
    if (typeof this.$store._mutations['app/setScroll'] !== 'undefined') {
      this.$store.commit('app/setScroll', {
        isScrolling: this.locomotive.scroll.isScrolling,
        limit: { ...e.limit },
        ...e.scroll // x, y
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Implementation

Before using our component in the page we declare it globally in /plugins/both.js.
/plugins/both.js is called in the Nuxt plugins configuration.

Once the plugin is global we can use it in our page or components in this way (/pages/index.vue):

<template>
  <LocomotiveScroll 
    ref="scroller" 
    :getted-options="{
      offset: ['30%',0],
      direction: 'horizontal'
      // Other options
    }">

    <!-- My Content:
    Html elements, Components...
    -->

  </LocomotiveScroll>
</template>
Enter fullscreen mode Exit fullscreen mode

You can access to locomotive scroll instance using this.$refs.scroller.locomotive.

Gotchas

Reactive elements alter the state of the application and DOM's elements could change.

This changes can take place in nested components and updating Locomotive Scroll could be complex.

We can use the $nuxt helper and emit a global event

this.$nuxt.$emit('update-locomotive')
Enter fullscreen mode Exit fullscreen mode

and listen it in the mounted hook in /LocomotiveScroll/component/index.vue component:

mounted () {
  this.$nuxt.$on('update-locomotive', () => {
    this?.locomotive?.update() // ?. is the Optional Chaining operator (https://www.joshwcomeau.com/operator-lookup?match=optional-chaining)
  })
}
Enter fullscreen mode Exit fullscreen mode

Examples

Basic Scroll

https://starter-kit-nuxt-locomotive-scroll.netlify.app/

Horizontal Scroll

https://starter-kit-nuxt-locomotive-scroll.netlify.app/horizontal-scroll/

Gsap Scroll Trigger

https://starter-kit-nuxt-locomotive-scroll.netlify.app/scroll-trigger/

Image Loads

https://starter-kit-nuxt-locomotive-scroll.netlify.app/image-loads/

On Call Function

https://starter-kit-nuxt-locomotive-scroll.netlify.app/on-call/

Thanks

If you find this repo useful and you saved time, well... let's take a coffee together!

https://www.buymeacoffee.com/davide_marchet

Discussion (13)

Collapse
jpcarpenter profile image
Jacob Carpenter

Thank you, thank you, thank you! I've tried to implement Locomotive Scroll in two other nuxt projects and ended up dumping it because of various errors I couldn't get past. This seems pretty straight forward. Can't wait to give it a try.

Collapse
didomarchet profile image
Davide Marchet Author

Hi, thanks! Let me know !

Collapse
jpcarpenter profile image
Jacob Carpenter

Unfortunately I get the same results as all the other times I've attempted to use this library. (mentioned in this issue: github.com/locomotivemtl/locomotiv...). Based on my days of searching for solutions, It seems like vue based web apps have these types of problems, that unfortunately no one has posted solutions to. ¯_(ツ)_/¯

Thread Thread
didomarchet profile image
Davide Marchet Author • Edited

Are you loading contents after the mounted hook?

Thread Thread
jpcarpenter profile image
Jacob Carpenter

I was fetching the content via asyncData in a page, then was calling this.$nuxt.$emit('update-locomotive') inside the mounted hook on the page to attempt to update the locomotive instance. I've tried this method of updating the instance before, and have never had any luck unfortunately. The issue is that the footer, and a few sections above the footer, are always hidden, or there is a huge amount of whitespace under the footer.

Thread Thread
didomarchet profile image
Davide Marchet Author

Hi, If you want, share with me a simplified version of the implementation of the starter-kit and the code with the problem.
I'll check as soon as I have a moment. Have a nice day!

Collapse
davidmarkl profile image
David Markl • Edited

Thank you for this great post. I only run into one Problem:
In my nuxt layout i have the nuxt component and a footer. Now if i wrap a page in the LocomotiveScroll component it will only calculate the height of the page and leaves my footer out. The result is that im not able to scroll to my footer beacause its height is not beeing respected in the locomotive calculation.
Do you have any good solution exept for just wrapping the whole layout in the Locomotive component?

Collapse
didomarchet profile image
Davide Marchet Author

Hi,
can you share the code ?
Have a nice day,

Davide

Collapse
davidmarkl profile image
David Markl

I reproduced my Problem.
Here is the Code:
github.com/davidmarkl/locomotive-s...
And here is it in production:
jolly-murdock-e4a35b.netlify.app/

Thread Thread
didomarchet profile image
Davide Marchet Author

Hi,
the issue is related to the css.
If you remove the overflow: hidden from html and body and you set as sizes {
....
height: 100vh;
overflow: hidden;
}

to .js-locomotive you have the locomotive wrapper and your footer.
But the page will have two scrollbar.

I think It's better use it ad a wrapper.

Have a nice day,

Davide

Collapse
luxdamore profile image
Luca Iaconelli

Cool! I've worked with Locomotive/Gsap/Nuxt for the last 2 months.. And sometimes it's not easy to get into the way they work! So, Good Job!! :)

JTK, if this is related to NuxtJs only, you should inject the LocomotiveScroll library in the ​plugin: nuxtjs.org/docs/2.x/directory-stru...

Collapse
didomarchet profile image
Davide Marchet Author • Edited

Hi,
thanks!
Locomotive Scroll is already injected into the plugin.
You can initialize Locomotive Scroll instances
where you want as many times as you want using:
const myLocomotive = new this.LocomotiveScroll({ el, ...options }) where el is your DOM element and options the instance options.
Is this what you were asking!?

Collapse
patsma profile image
Patryk Smakosz

Wow, thanks!

The exact thing I was looking for, especially the scroll-trigger integration :D
Cheers

Forem Open with the Forem app