Skip to content

Scroll linked animation

This example demonstrate the usage of the scrolled service and the ticked service to simply add some scroll-linked animations.

Pictures from picsum.photos

js
import { Base } from '@studiometa/js-toolkit';
import { map, damp, transform } from '@studiometa/js-toolkit/utils';

export default class ScrollLinkedAnimation extends Base {
  static config = {
    name: 'ScrollLinkedAnimation',
    refs: ['cols[]', 'offsetItem'],
  };

  scrollDeltaY = 0;

  dampedScrollDeltaY = 0;

  scrollProgressY = 0;

  dampedScrollProgressY = 0;

  parallaxOffsetHeight = 100;

  mounted() {
    this.parallaxOffsetHeight = this.$refs.offsetItem.offsetHeight / 2;
  }

  resized() {
    this.parallaxOffsetHeight = this.$refs.offsetItem.offsetHeight / 2;
  }

  scrolled(props) {
    // Enable `ticked` service when it is disabled and the user scrolls
    if (props.changed.y && !this.$services.has('ticked')) {
      this.$services.enable('ticked');
    }

    this.scrollProgressY = props.progress.y;
    this.scrollDeltaY = props.delta.y;
  }

  ticked() {
    // Read from the DOM and compute values in the method body
    this.dampedScrollDeltaY = damp(this.scrollDeltaY, this.dampedScrollDeltaY, 0.05, 0.0001);

    this.dampedScrollProgressY = damp(
      this.scrollProgressY,
      this.dampedScrollProgressY,
      0.25,
      0.0001,
    );

    const items = this.$refs.cols.map((col, index) => {
      const skewY = index % 2 ? this.dampedScrollDeltaY * -0.25 : this.dampedScrollDeltaY * 0.25;

      let translateY;
      if (index % 2 === 0) {
        translateY = map(
          this.dampedScrollProgressY,
          0,
          1,
          this.parallaxOffsetHeight,
          this.parallaxOffsetHeight * -1,
        );
      }

      return { col, skewY, translateY };
    });

    // Disable service when animation has ended
    if (this.dampedScrollDeltaY === this.scrollDeltaY) {
      this.$services.disable('ticked');
    }

    // Update and write to the DOM in the returned function to improve performance
    // and avoid layout trashing.
    return () => {
      items.forEach((item) => {
        transform(item.col, { skewY: item.skewY, y: item.translateY });
      });
    };
  }
}
html
<div data-component="ScrollLinkedAnimation" class="grid gap-10 grid-cols-3 p-10 my-10">
  <div data-ref="cols[]" class="grid gap-10">
    <img src="https://picsum.photos/seed/01/400/500" alt="" width="4000" height="5000" data-ref="offsetItem">
    <img src="https://picsum.photos/seed/02/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/03/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/04/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/05/400/500" alt="" width="4000" height="5000">
  </div>
  <div data-ref="cols[]" class="grid gap-10">
    <img src="https://picsum.photos/seed/11/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/12/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/13/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/14/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/15/400/500" alt="" width="4000" height="5000">
  </div>
  <div data-ref="cols[]" class="grid gap-10">
    <img src="https://picsum.photos/seed/21/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/22/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/23/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/24/400/500" alt="" width="4000" height="5000">
    <img src="https://picsum.photos/seed/25/400/500" alt="" width="4000" height="5000">
  </div>
</div>
js
import { Base, createApp } from '@studiometa/js-toolkit';
import ScrollLinkedAnimation from './ScrollLinkedAnimation.js';

class App extends Base {
  static config = {
    name: 'App',
    components: {
      ScrollLinkedAnimation,
    },
  };
}

export default createApp(App);

MIT Licensed