2 |
3 |
NUXT
4 |
5 | GSAP
6 |
7 |
8 |
--------------------------------------------------------------------------------
/constants/media.ts:
--------------------------------------------------------------------------------
1 | export const media = {};
2 |
--------------------------------------------------------------------------------
/interactions/classes/dom.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from "events";
2 | import { getElements } from "../utils";
3 |
4 | export class Dom extends EventEmitter {
5 | element: HTMLElement;
6 | secondaryElements: SecondaryElements;
7 |
8 | constructor({ selector, secondarySelectors }: SelectorProps) {
9 | super();
10 |
11 | const { element, secondaryElements } = getElements({
12 | selector,
13 | secondarySelectors,
14 | });
15 |
16 | this.element = element;
17 | this.secondaryElements = secondaryElements;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/interactions/classes/observer-animation.ts:
--------------------------------------------------------------------------------
1 | import { Dom } from "./dom";
2 | import gsap from "gsap";
3 |
4 | export class ObserverAnimation extends Dom {
5 | timeline: gsap.core.Timeline;
6 | observer: IntersectionObserver;
7 | target: ObserverAnimationProps["target"];
8 | animation: ObserverAnimationProps["animation"];
9 |
10 | constructor({
11 | selector,
12 | target,
13 | animation,
14 | initialState,
15 | }: ObserverAnimationProps) {
16 | super({
17 | selector,
18 | secondarySelectors: {},
19 | });
20 |
21 | initialState && this.initialize(initialState);
22 |
23 | this.timeline = gsap.timeline();
24 | this.animation = animation;
25 | this.target = target;
26 |
27 | this.createObserver();
28 | }
29 |
30 | createObserver() {
31 | const callback = (entries: IntersectionObserverEntry[]) => {
32 | entries.forEach(entry => {
33 | entry.isIntersecting ? this.animateIn() : this.animateOut();
34 | });
35 | };
36 |
37 | this.observer = new window.IntersectionObserver(callback, {
38 | threshold: this.animation?.threshold ?? 0.5,
39 | rootMargin: this.animation?.rootMargin,
40 | root: this.animation?.root,
41 | });
42 |
43 | this.observer.observe(this.target ?? this.element);
44 | }
45 |
46 | initialize(tween: gsap.TweenVars) {
47 | gsap.set(this.element, {
48 | ...tween,
49 | });
50 | }
51 |
52 | animateIn() {
53 | return new Promise(resolve => {
54 | this.timeline.to(this.element, {
55 | ...this.animation?.tween,
56 | });
57 |
58 | this.timeline.call(resolve);
59 | });
60 | }
61 |
62 | animateOut() {
63 | return new Promise(resolve => {
64 | if (this.animation?.resetOnExit) {
65 | this.timeline.reverse();
66 | }
67 |
68 | this.timeline.call(resolve);
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/interactions/classes/scroll-animation.ts:
--------------------------------------------------------------------------------
1 | import gsap from "gsap";
2 | import { Dom } from "./dom";
3 |
4 | export class ScrollAnimation extends Dom {
5 | timeline: GSAPTimeline;
6 |
7 | constructor({ element, animationProps }: ScrollAnimationProps) {
8 | super({ selector: element, secondarySelectors: {} });
9 | this.animate(animationProps);
10 | }
11 |
12 | animate(props: ScrollAnimationProps["animationProps"]) {
13 | const { scrub, trigger, start, ...rest } = props;
14 |
15 | gsap.set(this.element, {
16 | ...props.initialState,
17 | });
18 |
19 | this.timeline = gsap.timeline({
20 | scrollTrigger: {
21 | ...rest,
22 | start: start ?? "top bottom",
23 | scrub: scrub ?? 0.7,
24 | trigger: trigger ?? this.element,
25 | },
26 | });
27 |
28 | props.function && props.function(this.timeline);
29 | }
30 |
31 | async kill() {
32 | await this.timeline?.kill();
33 | return Promise.resolve();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/interactions/components/hello.ts:
--------------------------------------------------------------------------------
1 | import gsap from "gsap";
2 | import { Dom } from "../classes/dom";
3 | import { ScrollAnimation } from "../classes/scroll-animation";
4 |
5 | export class Hello extends Dom {
6 | timeline: GSAPTimeline;
7 | animations: ScrollAnimation[] = [];
8 |
9 | constructor() {
10 | super({
11 | selector: ".hello",
12 | secondarySelectors: {
13 | dash: ".hello__dash",
14 | },
15 | });
16 |
17 | this.timeline = gsap.timeline();
18 | this.init();
19 | }
20 |
21 | init() {
22 | this.animations.push(
23 | new ScrollAnimation({
24 | element: this.secondaryElements.dash[0],
25 | animationProps: {
26 | pin: true,
27 | trigger: this.element,
28 | start: "top top",
29 |
30 | function: tl => {
31 | tl.to(this.secondaryElements.dash[0], {
32 | width: "40vw",
33 | });
34 | },
35 | },
36 | })
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/interactions/index.ts:
--------------------------------------------------------------------------------
1 | import gsap from "gsap";
2 | import Lenis from "@studio-freight/lenis";
3 | import ScrollTrigger from "gsap/ScrollTrigger";
4 | import { Dom } from "./classes/dom";
5 | import { Hello } from "./components/hello";
6 |
7 | export class Interactions {
8 | components: { [x: string]: Dom } = {};
9 |
10 | constructor() {
11 | gsap.registerPlugin(ScrollTrigger);
12 |
13 | this.onResize();
14 | this.createSmoothScroll();
15 | this.createPreloader();
16 | this.addEventListeners();
17 | }
18 |
19 | createSmoothScroll() {
20 | const lenis = new Lenis({
21 | lerp: 0.1,
22 | });
23 |
24 | lenis.on("scroll", ScrollTrigger.update);
25 |
26 | gsap.ticker.add(time => {
27 | lenis.raf(time * 1000);
28 | });
29 |
30 | gsap.ticker.lagSmoothing(0);
31 | }
32 |
33 | createPreloader() {
34 | /**
35 | * - preload media
36 | * - initializations
37 | * - call onpreloaded on conpletion
38 | * */
39 | this.onPreloaded();
40 | }
41 |
42 | onPreloaded() {
43 | this.components.hello = new Hello();
44 | }
45 |
46 | onResize() {
47 | /**
48 | * - recalculate
49 | * - reinitialise
50 | * - reset, etc.
51 | * */
52 | }
53 |
54 | addEventListeners() {
55 | window.addEventListener("resize", this.onResize.bind(this));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/interactions/types.ts:
--------------------------------------------------------------------------------
1 | interface SelectorProps {
2 | selector: string | HTMLElement;
3 | secondarySelectors: {
4 | [x: string]: string | HTMLElement;
5 | };
6 | }
7 |
8 | interface ScrollAnimationSettings {
9 | initialState?: gsap.TweenVars;
10 | function?: (tl: GSAPTimeline) => void;
11 | }
12 |
13 | interface ScrollAnimationProps {
14 | element: string | HTMLElement;
15 | animationProps: ScrollTrigger.Vars & ScrollAnimationSettings;
16 | }
17 |
18 | type SecondaryElements = {
19 | [x: string]: HTMLElement[];
20 | };
21 |
22 | interface ObserverAnimationProps
23 | extends Omit