Jump to content
Search Community

GSAP + Locomotive Scroll affecting page layout

Inkblot test
Moderator Tag

Recommended Posts

Newbie here, I'm using Vue (3.0) and I'm trying to get started with GSAP and Locomotive Scroll but ran into a couple of issues. I've tried using some sample code(bottom of post - mine is without the code to scroll images) to get smooth scrolling implemented but I've found that it adversely affects the layout of my page.

 

A page is a div element, styled by:

.full-page {
    min-width: 100%;
    width: 100%;
    min-height: 100vh;
}

So the structure of the application is:

<html>
  <body>
    <div id="#app">
      <div class="full-page">
        ...
      </div>
    </div>
  </body>
</html>

Here is my App single file component where the scrolling code is:

<template>
    <router-view></router-view>
</template>

<script>
    import {gsap} from 'gsap';
    import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
    import LocomotiveScroll from 'locomotive-scroll';

    export default {
        name: 'App',
        computed: {
            tag() {
               return this.scroller.tagName;
            },
            scroller() {
                return document.scrollingElement;
            }
        },
        mounted() {
            this.scroller.setAttribute("data-scroll-speed", "0.5")
            this.scroller.setAttribute("data-scroll", "")

            const locoScroll = new LocomotiveScroll({
                el: this.scroller,
                smooth: true
            });

            gsap.registerPlugin(ScrollTrigger);
            locoScroll.on("scroll", ScrollTrigger.update);

            ScrollTrigger.scrollerProxy(this.tag, {
                scrollTop(value) {
                    return arguments.length ?
                        locoScroll.scrollTo(value, 0, 0) :
                        locoScroll.scroll.instance.scroll.y
                },
                getBoundingClientRect() {
                    return {
                        left: 0, top: 0,
                        width: window.innerWidth,
                        height: window.innerHeight
                    }
                },
                pinType: this.scroller.style.transform ? "transform" : "fixed"
            });

            ScrollTrigger.addEventListener("refresh", () => locoScroll.update());
            ScrollTrigger.refresh();
        }
    }
</script>

<style scoped>

</style>

Note that the scrolling element is the html tag (document.documentElement).

 

The scrolling speed hasn't changed. I'm also using a custom cursor which has size 64px by 64px and when I move it to the edges it makes the scrollbars appear (I can confirm this is because of GSAP + Locomotive scroll as everything is fine without it). The second issue that I cannot move the cursor past 100vh (you know, to scroll to the rest of the page) - I can scroll the page, just the cursor doesn't come with. Also, position: fixed elements also do not pass the 100vh point (again, works fine without GSAP) and hence they are lost when I scroll down. There's something I'm clearly missing about correctly structuring pages.

 

I would like to keep the HTML element as my scrolling element due to the current structure of the app - is it a requirement that the scroll container cannot be the HTML/body elements? After wrapping the html in the codepen link in html and body tags and then adding the required attributes to the html tag manually, the scrolling still works as expected so I'm not sure what's going wrong. Any and all help is greatly appreciated.

See the Pen WNwjEaL by ebinabo (@ebinabo) on CodePen

Link to comment
Share on other sites

Hey Inkblot and welcome to the GreenSock forums.

 

26 minutes ago, Inkblot said:

I would like to keep the HTML element as my scrolling element due to the current structure of the app - is it a requirement that the scroll container cannot be the HTML/body elements?

This is a requirement of smooth scrolling libraries like Locomotive. This is because virtual scrolling (the technique that smooth scrolling libraries need to use) has to be independent from the normal scrolling in order to keep things perfectly smooth. It's not a requirement of ScrollTrigger. 

 

28 minutes ago, Inkblot said:

I'm also using a custom cursor which has size 64px by 64px and when I move it to the edges it makes the scrollbars appear

You should be able to fix this by putting the cursor in a container whose dimensions are the same as the viewport and hiding the overflow for that container.

 

30 minutes ago, Inkblot said:

position: fixed elements also do not pass the 100vh point ... and hence they are lost when I scroll down.

I'm guessing that these elements are within your smooth scrolling container. They should be outside of the smooth scrolling container and then they should work normally.

 

28 minutes ago, Inkblot said:

The second issue that I cannot move the cursor past 100vh

This exact reason for why this is happening depends on how you have it set up. Given you don't show it in your demo we can't say for sure.

 

I would recommend using fixed positioning for this element (making sure it's outside of the smooth scrolling container as instructed above) and refactor your code to work with that if it didn't already have fixed positioning.

 

Ultimately none of your questions have to do with GSAP or ScrollTrigger specifically but hopefully the above is helpful anyways :) 

  • Like 1
Link to comment
Share on other sites

Hey Zach, thanks for the warm welcome.

 

I'd figured it was just a case of me not fully understanding the requirements haha. I've tried your solution but I've run into a bit of a problem that's preventing me from seeing if it's worked.

 

The issues with the viewport are fixed but it seems the scrolling element I've defined doesn't change to the new one when going to different pages, meaning I get an error. It happens because my App component is only mounted once on the first load and isn't reloaded on a route change so the scroller method is only fired once.  I tried to remedy the this with some Vue stuff and by stopping and destroying the first LocomotiveScroll object and creating a new one but I've run into a bit of a brick wall. Here's the modified code (in the script tag of the sfc):

 

import {gsap} from 'gsap';
import {ScrollTrigger} from "gsap/dist/ScrollTrigger";
import LocomotiveScroll from 'locomotive-scroll';

export default {
    name: 'App',
    data() {
      return {
          locoScroll: null,
        }
    },
    computed: {
        tag() {
            return '.page .full-page';
        }
    },
    watch: {
        // When the route changes ...
        $route(to, from) {
            this.$nextTick(this.setScroll);
        }
    },
    methods: {
        scroller() {
            const scroller = document.querySelector(this.tag);
            if (this.locoScroll !== null) {
                this.locoScroll.stop();
                this.locoScroll.destroy();
            }
            scroller.setAttribute("data-scroll-speed", "0.5");
            scroller.setAttribute("data-scroll", "");

            return scroller;
        },
        setScroll() {
            const scroller = this.scroller();
            let locoScroll;
            this.locoScroll = locoScroll = new LocomotiveScroll({
                el: scroller,
                smooth: true
            });
            gsap.registerPlugin(ScrollTrigger);
            locoScroll.on("scroll", ScrollTrigger.update);
            ScrollTrigger.scrollerProxy(this.tag, {
                scrollTop(value) {
                    return arguments.length ?
                        locoScroll.scrollTo(value, 0, 0) :
                        locoScroll.scroll.instance.scroll.y
                },
                getBoundingClientRect() {
                    return {
                        left: 0, top: 0,
                        width: window.innerWidth,
                        height: window.innerHeight
                    }
                },
                pinType: scroller.style.transform ? "transform" : "fixed"
            });
            ScrollTrigger.addEventListener("refresh", () => locoScroll.update());
            ScrollTrigger.refresh();
        }
    },
    mounted() {
        // Now that it's a div it is mounted after the app is, so wait until all content loaded
        window.addEventListener("load", this.setScroll);
    }
}

I'm now met with a Uncaught (in promise) TypeError: Cannot read property 'match' of undefined

   if (window.getComputedStyle) {
                var n = getComputedStyle(t)
                  , r = n.transform || n.webkitTransform || n.mozTransform
                  , i = r.match(/^matrix3d\((.+)\)$/); // Here
                return i ? (e.x = i ? parseFloat(i[1].split(", ")[12]) : 0,
                e.y = i ? parseFloat(i[1].split(", ")[13]) : 0) : (i = r.match(/^matrix\((.+)\)$/),
                e.x = i ? parseFloat(i[1].split(", ")[4]) : 0,
                e.y = i ? parseFloat(i[1].split(", ")[5]) : 0),
                e
            }

Could it be because I'm re-registering the plugin on each page load? This all seems a bit hacky and I'm sure there's a better way to go about this but my knowledge is very limited.

Link to comment
Share on other sites

That again is more of a Vue question than a GSAP one. Registering plugins multiple times shouldn't cause any issues. However you should make sure to kill off old ScrollTriggers that are no longer used and set up any ScrollTriggers that you need to after the new content has loaded.

 

It would likely help to make a minimal demo of the issue with everything that's irrelevant stripped out not only for your own understanding but also so we can take a look at exactly what's happening with the full context. Usually it's best to recreate the situation from the ground up.

Link to comment
Share on other sites

I've setup a Vue 3 codepen 

See the Pen XWjRgbN by Inkblotter (@Inkblotter) on CodePen

 which mirrors my structure, although, nothing seems to be happening in regards to the scrolling animation. Have I not set the CSS correctly?

 

EDIT:

It seems as if explicitly setting a height and/or overflow attribute disables the scrolling animation. That solves that but my previous reply was an error caused when switching pages, something I don't think I can really demonstrate in a codepen.

Link to comment
Share on other sites

I've setup a codesandbox but I don't get the error in question ... the locomotive scroll also doesn't work and I'm really not sure what's gone wrong given that the same code works in codepen.

 

However, my main issue is that where I create the LocomotiveScroll object I have to supply the DOM element which changes with each page and I think that's where the problem arises from because if I remove my code to setup the scrolling again on a route change, I no longer get the error. For reference:

  watch: {
    $route(to, from) {
      this.$nextTick(this.setScroll);
    },
  },

But then, when I switch pages the locomotive scroll doesn't work (because the scrolling container is the one from the previous page) but manually reloading the page solves this. Moving this code to the actual Page component fixes it - the container correctly updates (as it should) but the error still appears.

Link to comment
Share on other sites

After some more digging I've found that the following two lines we're causing the errors:

ScrollTrigger.addEventListener('refresh', scroller.update)
ScrollTrigger.refresh()

Looking over the docs for ScrollTrigger, I added the true as a parameter to ScrollTrigger.refresh and it worked - there doesn't seem to be a similar way of doing things for LocomotiveScroll however, so I currently just catch the exception thrown:

ScrollTrigger.addEventListener('refresh', () => {
    try {
        scroller.update()
        console.log("scroller updated")
    } catch {
        console.log("could not update scroller")
    }
})

I think that's about it for getting the scrolling to work (besides getting the desired appearance and animations) but I'm still not sure if I've done things correctly here as the error still occurs on resizes so I had to modify the line:

scroller.on('scroll', ScrollTrigger.update)

To

scroller.on('scroll', () => {
    try {
        ScrollTrigger.update();
        console.log("scroll trigger updated")
    } catch {
        console.log("error on resize caught")
    }
});
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...