Using request animation frame to keep it smooth. Combining it with matching body height to the height of my 'no-scroll' element. Meaning that it matches the height of the body. (Ignoring the sticky header ofcourse)

Mainly inspired by this pen I'd thought I get it to work with nuxt.js. Though mine does not use GSAP.

First thing was setting up scroll events listeners to register event changes to:

  • Resize body when needed
  • Change our target scroll value when user scrolls
Along with sizing out body height to match content.


            mounted() {
                document.addEventListener('scroll', this.callScroll);
                document.addEventListener('resize', this.callResize);
                window.setTimeout(() => {
                    document.body.style.height = `${this.$el.querySelector('.scroll-wrapper').clientHeight + 40}px`;
                }, 100);
            },

            methods: {
                callResize(event) {
                    document.body.style.height = `${this.$el.querySelector('.scroll-wrapper').clientHeight + 40}px`;
                },

                callScroll(event) {
                    this.scroll.target = window.scrollY;
                    requestAnimationFrame(this.scrollCall);
                },
            }
            

Oh and to clear up once we are done/leave the page. Reset body height + clear event listeners using the beforeDestroy method


            beforeDestroy() {
                document.removeEventListener('scroll', this.callScroll);
                document.removeEventListener('resize', this.callResize);
                document.body.style.height = '';
            },
            

Now the logic to handle the scroll is basically using transform3d on the scroll-wrapper We have a seperate variable to keep track of the current position on the page (actual location) and the animating one (the one we using to translate the page with)


            {
                scroll: {
                    target: 0,
                    y: 0,
                }
            }
            

Where scroll.y is what we are using to transform. While scroll.target is our actual page position.

ScrollCall()

Now when we scroll.target even though we aren't really using it but nice to have as well a record. We also start our requestAnimationFrame


            scrollCall() {
                const scrollY = window.scrollY;
                /** Really crappy easing */
                this.scroll.y += (scrollY - this.scroll.y) * 0.005;

                /** Are we less than 0.05 pixels away from our scroll? */
                if (Math.abs(scrollY - this.scroll.y) < 0.05) {
                    this.scroll.y = scrollY;
                } else {
                    /** Rely on requestAnimationFrame for smooth AF animation */
                    requestAnimationFrame(this.scrollCall);
                }
            }