Jump to content
Search Community

GSAP + React + Gatsby

thmsmtylr test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi there,

 

First post here! I'm working on a React/Gatsby app which references this tutorial. I've solved most of the issues except for the following error:

 

TypeError: Cannot read property 'easeOut' of undefined

 

I've tried a bunch of variations of imports destructuring etc but Quad is never defined.

 

 My code is as follows:

 

import React, { Component } from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
import { TweenMax, Quad, Sine, Back  } from "gsap/TweenMax"
const prettyLetter = require( 'pretty-letters' )

export default class IndexPage extends Component {
  componentDidMount() {
    var options = {
      groupClass: 'char-group-',
      groupTag: 'span'
    }

    prettyLetter('a', options)

    const lineEq = (y2, y1, x2, x1, currentVal) => {
      // y = mx + b
      var m = (y2 - y1) / (x2 - x1), b = y1 - m * x1
      return m * currentVal + b
    }

    const lerp = (a,b,n) => (1 - n) * a + n * b

    const distance = (x1,x2,y1,y2) => {
      var a = x1 - x2
      var b = y1 - y2
      return Math.hypot(a,b)
    }

    const getMousePos = (e) => {
      let posx = 0
      let posy = 0
      if (!e) e = window.event
      if (e.pageX || e.pageY) {
        posx = e.pageX
        posy = e.pageY
      }
      else if (e.clientX || e.clientY) 	{
        posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
        posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
      }
      return { x : posx, y : posy }
    }

    let winsize;
    const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}
    calcWinsize()
    window.addEventListener('resize', calcWinsize)

    // The feDisplacementMap element
    const feDisplacementMapEl = document.querySelector('feDisplacementMap')

    class Menu {
      constructor() {
        this.DOM = {
          svg: document.querySelector('svg.distort'),
          menu: document.querySelector('nav.menu')
        }
        // The images (one per menu link)
        this.DOM.imgs =  Array.from(Object.assign.apply(Object, [this.DOM.svg.querySelectorAll('g > image')]))
        // The menu links
        this.DOM.menuLinks = Array.from(Object.assign.apply(Object, [this.DOM.menu.querySelectorAll('.menu__link')]))
        // Mouse position
        this.mousePos = {x: winsize.width/2, y: winsize.height/2}
        // Last mouse positions (one to consider for the image translation movement, another for the scale value of the feDisplacementMap element)
        this.lastMousePos = {
          translation: {x: winsize.width/2, y: winsize.height/2},
          displacement: {x: 0, y: 0}
        }
        // feDisplacementMap scale value
        this.dmScale = 0
        // Current menu link position
        this.current = -1

        this.initEvents()
        requestAnimationFrame(() => this.render())
      }

      initEvents() {
        // Update mouse position
        window.addEventListener('mousemove', ev => this.mousePos = getMousePos(ev))

        this.DOM.menuLinks.forEach((item, pos) => {
          const letters = Array.from(Object.assign.apply(Object, [item.querySelectorAll('span')]))

          const mouseenterFn = () => {
            // Hide the previous menu image
            if (this.current !== -1) {
              TweenMax.set(this.DOM.imgs[this.current], {
                opacity: 0
              });
            }
            // Update current
            this.current = pos

            // Now fade in the new image if we are entering the menu or just set the new image's opacity to 1 if switching between menu items
            if (this.fade) {
              TweenMax.to(this.DOM.imgs[this.current], 0.5, {
                ease: Quad.easeOut, opacity: 1
              })
              this.fade = false
            } else {
              TweenMax.set(this.DOM.imgs[this.current], {
                opacity: 1
              })
            }

            // Letters effect
            TweenMax.staggerTo(letters, 0.2, {
              ease: Sine.easeInOut,
              y: this.lastMousePos.translation.y < this.mousePos.y ? 30 : -30,
              startAt: {opacity: 1, y: 0},
              opacity: 0,
              yoyo: true,
              yoyoEase: Back.easeOut,
              repeat: 1,
              stagger: {
                grid: [1,letters.length-1],
                from: 'center',
                amount: 0.12
              }
            })
          }
          item.addEventListener('mouseenter', mouseenterFn)
        })

        const mousemenuenterFn = () => this.fade = true
        const mousemenuleaveFn = () => TweenMax.to(this.DOM.imgs[this.current], .2, {
          ease: Quad.easeOut, opacity: 0
        })

        this.DOM.menu.addEventListener('mouseenter', mousemenuenterFn)
        this.DOM.menu.addEventListener('mouseleave', mousemenuleaveFn)
      }

      render() {
        // Translate the image on mousemove
        this.lastMousePos.translation.x = lerp(this.lastMousePos.translation.x, this.mousePos.x, 0.2)
        this.lastMousePos.translation.y = lerp(this.lastMousePos.translation.y, this.mousePos.y, 0.2)
        this.DOM.svg.style.transform = `translateX(${(this.lastMousePos.translation.x-winsize.width/2)}px) translateY(${this.lastMousePos.translation.y-winsize.height/2}px)`

        // Scale goes from 0 to 50 for mouseDistance values between 0 to 140
        this.lastMousePos.displacement.x = lerp(this.lastMousePos.displacement.x, this.mousePos.x, 0.1)
        this.lastMousePos.displacement.y = lerp(this.lastMousePos.displacement.y, this.mousePos.y, 0.1)
        const mouseDistance = distance(this.lastMousePos.displacement.x, this.mousePos.x, this.lastMousePos.displacement.y, this.mousePos.y)
        this.dmScale = Math.min(lineEq(50, 0, 140, 0, mouseDistance), 50)
        feDisplacementMapEl.scale.baseVal = this.dmScale

        requestAnimationFrame(() => this.render())
      }
    }

    new Menu()
  }

  render() {
    return (
      <Layout>
        <SEO title="Home" keywords={[`Artist`, `Brisbane-based`, `drawing`, `painting`, `watercolour`, `sculpture`, `installation`, `video`, `embroidery`]} />
        <div style={{ paddingBottom: 100 }}>
          <svg className="distort" width="350" height="450" viewBox="0 0 350 450">
            <filter id="distortionFilter">
              <feTurbulence type="turbulence" baseFrequency="0.07 0.01" numOctaves="5" seed="2" stitchTiles="stitch" x="0%" y="0%" width="100%" height="100%" result="noise"/>
              <feDisplacementMap in="SourceGraphic" in2="noise" scale="0" xChannelSelector="R" yChannelSelector="B" x="0%" y="0%" width="100%" height="100%" filterUnits="userSpaceOnUse"/>
            </filter>
            <g filter="url(#distortionFilter)">
              <image className="distort__img" x="50" y="50" xlinkHref={require('../images/1.jpg')} height="350" width="250"/>
              <image className="distort__img" x="50" y="50" xlinkHref={require('../images/2.jpg')} height="350" width="250"/>
            </g>
          </svg>
          <nav className="menu">
					  <a href="#" className="menu__link">Shanghai</a>
					  <a href="#" className="menu__link">Taipei</a>
					  <a href="#" className="menu__link">Bangkok</a>
					  <a href="#" className="menu__link">Kyoto</a>
				  </nav>
        </div>
      </Layout>
    )
  }
}

 

Edited by thmsmtylr
More details re imports
Link to comment
Share on other sites

Try using Power1 instead of Quad. Many years ago, we replaced "Quad", "Cubic", "Quart", and "Quint" with "Power1", "Power2", "Power3", and "Power4" because those names made it much easier to understand and link the effect with the name. I personally could never quite remember which of the old names was most or least powerful/extreme. 

 

Happy tweening!

  • Like 2
Link to comment
Share on other sites

  • 4 months later...

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...