ashthornton / asscroll

Ash's Smooth Scroll 🍑
MIT License
946 stars 27 forks source link

Examples for common frameworks #15

Closed urbgimtam closed 3 years ago

urbgimtam commented 3 years ago

Not really an issue, but would be so nice if there where examples for various common js frameworks, like Vuejs, Nuxtjs, React, etc.

In Nuxtjs, I'm struggling with asscroll breaking scroll between pages.

ashthornton commented 3 years ago

Hi @urbgimtam, I'm actually not that experienced with those frameworks but I know the concept would be the same as using something like Highway in that you are in a stateful environment.

The core idea is that you call ASScroll.disable() when leaving or changing a page, then call ASScroll.enable() once your new DOM has been added, then update the third parameter of the enable() function with your new scroll target.

Or if your scroll target hasn't changed, you can just call ASScroll.onResize() if it's just your content that has changed the size of the page.

Usage in Highway would look similar to:

onEnter() {
    const page = this.wrap.lastElementChild
    ASScroll.enable(false, true, page)
}

onLeave() {
    ASScroll.disable()
}

I will think about adding some framework specific examples at some point. But does this help at all?

urbgimtam commented 3 years ago

Thanks for the quick reply, and also for an awesome scroller (I really like it).

I've been trying something similar, without success. Also, don't want to do it on every page, so I'm trying to listen to events and then disable and enable on route change.

Currently I'm able to receive an 'routeChange' event, but either that's already too late to disable (?), or I'm messing up...

As I'm using Server-Side-Rendering (first page call is rendered from server, then 'hydrated'. Next routes are just js), I can see that 'document.body.scrollHeight' is correct on the first page (SSR), but on next routes it doesn't update.

Without ASScroll, 'document.body.scrollHeight' gets updated between routes. As I'm using GSAP ScrollTrigger.proxy(), it may also be an issue, but doesn't seem like it.

Here's what I'm using:

// ../plugins/asscroll_gsap,js
// In Nuxt, plugins are available all accross the app

import Vue from 'vue'
import { gsap, ScrollTrigger } from 'gsap/all'
import ASScroll from '@ashthornton/asscroll';

gsap.registerPlugin(ScrollTrigger)

const asscrollOptions = {
  // disableRaf: true,
  // ease: 0.075 // default
  // ease: 0.10 // default
  // customScrollbar: true
}

const asScroll = new ASScroll(asscrollOptions)

document.documentElement.scrollTop = 0

// each time asScroll updates, tell ScrollTrigger to update too (sync positioning)
asScroll.on('scroll', ScrollTrigger.update)

ScrollTrigger.defaults({
  scroller: '.innerscroller',
  markers: true
})

ScrollTrigger.scrollerProxy(
    '.innerscroller', {
      scrollTop(value) {
        return arguments.length ?
          asScroll.scrollTo(value) :
          -asScroll.smoothScrollPos
      }, // we don't have to define a scrollLeft because we're only scrolling vertically.
      getBoundingClientRect() {
        return {
          top: 0,
          left: 0,
          width: window.innerWidth,
          height: window.innerHeight
        }
      }
    }
  )

asScroll.on('raf', ScrollTrigger.update)
// gsap.ticker.add(asScroll.onRaf)   // What would be the benefit of this? When used, it stutters, sometimes.

ScrollTrigger.addEventListener('refresh', () => asScroll.onResize())
ScrollTrigger.addEventListener('resize', () => asScroll.onResize())

// after everything is set up, refresh() ScrollTrigger and update asscroll because padding may have been added for pinning, etc.
ScrollTrigger.refresh()

asScroll.enable(false, true, document.querySelector('.innerscroller'))

// Listen to routeChanged events on Nuxtjs
window.onNuxtReady((app) => {
  console.log('app is ready');
  app.$nuxt.$on('routeChanged', (to, from) => {
    console.log('Route has changed!', to, from);
    asScroll.onResize()
    asScroll.disable()
    asScroll.enable(false, true, document.querySelector('.innerscroller'))
    asScroll.scrollTo(0)
    ScrollTrigger.refresh()
  })
})

// Adding GSAP and ScrollTrigger to plugin, so its already loaded on every page
Object.defineProperty(Vue.prototype, 'gsap', {
  value: gsap
});

Object.defineProperty(Vue.prototype, 'ScrollTrigger', {
  value: ScrollTrigger
});
urbgimtam commented 3 years ago

Please forget my previous example, I was doing it incorrectly. Although is not exactly what I wanted, I've managed to do it in a Nuxt-friendly way. Thank you :)

For future reference, here's what I've done in Nuxt: (I've also prepared a codesandbox: https://codesandbox.io/s/wizardly-sanderson-dux5u?file=/plugins/asscroll.js:0-1677)

//plugins/asscroll.js

import Vue from "vue";
import ASSCroll from "@ashthornton/asscroll";
import { gsap, ScrollTrigger } from "gsap/all";

gsap.registerPlugin(ScrollTrigger);

const asscrollOptions = {
  // disableRaf: true,
  // ease: 0.075 // default
  // customScrollbar: true
};

const asScroll = new ASSCroll(asscrollOptions);

document.documentElement.scrollTop = 0;

// each time asScroll updates, tell ScrollTrigger to update too (sync positioning)
asScroll.on("scroll", ScrollTrigger.update);

ScrollTrigger.defaults({
  scroller: ".innerscroller",
//  markers: true
});

ScrollTriggerProxy();

asScroll.on("raf", ScrollTrigger.update);

ScrollTrigger.addEventListener("refresh", () => asScroll.onResize());

// after everything is set up, refresh() ScrollTrigger and update asscroll because padding may have been added for pinning, etc.
ScrollTrigger.refresh();

asScroll.enable(false, true, document.querySelector(".innerscroller"));
asScroll.onResize(window.innerWidth, window.innerHeight);

function ScrollTriggerProxy() {
  return ScrollTrigger.scrollerProxy(".innerscroller", {
    scrollTop(value) {
      return arguments.length
        ? asScroll.scrollTo(value)
        : -asScroll.smoothScrollPos;
    }, // we don't have to define a scrollLeft because we're only scrolling vertically.
    getBoundingClientRect() {
      return {
        top: 0,
        left: 0,
        width: window.innerWidth,
        height: window.innerHeight
      };
    }
  });
}

Object.defineProperty(Vue.prototype, "gsap", {
  value: gsap
});
Object.defineProperty(Vue.prototype, "ScrollTrigger", {
  value: ScrollTrigger
});

export default (context, inject) => {
  inject("asScroll", asScroll);
};

// mixins/global.js

// mixins can be imported to each page required, they'll be "injected" in the correct lifecycle hook (in this case mounted() and beforeDestroy()).

export default {
  mounted() {
    // Comes from plugin
    this.$asScroll.enable(false, true, document.querySelector(".innerscroller"));
  },

  beforeDestroy() {
    // Comes from plugin
    this.$asScroll.disable();
  }
};

Finally, on each page:

// example: /pages/index.vue

<template>
  <section>
    <div>
      <Logo :width="350"/>
      <h1 class="title">
        NUXT
        <span class="green">JS</span>
      </h1>
      <h2 class="subtitle">Starter for CodeSandBox</h2>
      <div class="text">
        <p>Bacon ipsum dolor amet short ribs fatback ham shank chislic boudin jowl turkey kevin. Jowl ribeye bacon buffalo pastrami pork chop brisket leberkas turducken frankfurter pig pork loin short loin. Cupim brisket jerky, beef ribs biltong short ribs shoulder leberkas pig shank landjaeger prosciutto strip steak rump ham. Chicken swine t-bone ground round leberkas. Corned beef short loin tongue t-bone doner chicken, porchetta bresaola ball tip jowl boudin pork chop meatball rump chislic. Ham hock ball tip filet mignon meatloaf, pork belly picanha shoulder turducken burgdoggen chuck leberkas rump. Beef ribs pork loin strip steak, shoulder burgdoggen ham hock short loin pig pancetta.</p>
        <p>Swine shankle beef, tongue corned beef boudin alcatra. Picanha drumstick meatloaf filet mignon, shoulder corned beef shankle sausage strip steak hamburger turkey fatback tri-tip spare ribs shank. Swine bresaola rump pancetta capicola pork belly leberkas prosciutto short loin shoulder filet mignon jerky kevin chicken shankle. Beef ribs tail t-bone kielbasa biltong frankfurter doner. Venison brisket sausage, biltong sirloin andouille frankfurter pork flank. Corned beef chicken beef ribs jerky tri-tip.</p>
        <p>Spare ribs boudin kielbasa fatback pork chop, meatloaf jerky frankfurter shoulder bacon chislic t-bone cupim. Strip steak short ribs chislic, beef ribs picanha tenderloin rump swine ham meatball meatloaf andouille flank pancetta. Jerky sirloin swine ball tip strip steak. Alcatra swine beef ribeye. Bresaola short loin shankle bacon. Swine frankfurter chuck cow. Boudin venison strip steak filet mignon meatloaf rump pork belly sirloin pork chop jerky frankfurter beef fatback pork loin flank.</p>
        <p>Landjaeger hamburger prosciutto kevin flank short loin, pig beef ribeye rump spare ribs t-bone pork loin tri-tip porchetta. Ground round sausage boudin pancetta filet mignon. Landjaeger pork chop pork loin, meatball salami corned beef shank cow ham hock porchetta pastrami sirloin capicola alcatra. Cow venison salami biltong porchetta tri-tip ground round landjaeger. Turducken spare ribs picanha meatloaf bacon ball tip doner. Sausage pork belly frankfurter doner tenderloin drumstick tail tongue.</p>
        <p>Venison biltong jowl hamburger fatback turkey tri-tip bacon filet mignon jerky. Prosciutto turducken ham tri-tip kielbasa capicola frankfurter shankle landjaeger tongue ribeye. Strip steak ball tip bresaola pastrami shoulder cupim sausage turducken corned beef rump. Cupim alcatra swine fatback pig cow filet mignon chicken chislic shank short loin ham hock pork landjaeger picanha. Pork chop pork loin jerky short loin flank filet mignon ham. Chislic beef spare ribs pig filet mignon turkey, short ribs picanha shank salami venison shankle porchetta. Pancetta meatloaf doner shankle sirloin meatball prosciutto jowl.</p>
      </div>
      <div class="links">
        <a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation
          <IconLink/>
        </a>

        <NLink to="/about" class="button--grey">About</NLink>
      </div>
    </div>
  </section>
</template>

<script>
import Logo from "~/components/Logo.vue";
import IconLink from "~/components/IconLink.vue";
import globalMixin from "~/mixins/global.js";

export default {
  components: {
    Logo,
    IconLink
  },

  mixins: [globalMixin]
};
</script>

<style scoped>
.title {
  font-family: "Quicksand", "Source Sans Pro", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  display: block;
  font-weight: 400;
  font-size: 100px;
  color: #2E495E;
  letter-spacing: 1px;
  font-size: 6em;
}
.green {
  color: #00C48D;
}

.subtitle {
  font-weight: 300;
  font-size: 3em;
  color: #2E495E;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}

.text {
  max-width: 600px;
  margin: auto;
}

p {
  margin: 40px auto;
  text-align: left;
}
</style>
m90khan commented 2 years ago

Any example to implement this in React ?

arturs706 commented 2 years ago

How to implement this in React?

Satyam-code143 commented 2 years ago

@m90khan @arturs706

Hey!

Did you get any examples for implementing in react?

m90khan commented 2 years ago

nope. better use smooth-scroll

Satyam-code143 commented 2 years ago

@m90khan @arturs706

I got it working with some help. Here is a StackOverflow question that helped me solve the same.

Here is the Link StackOverflow Question

Thank you!!