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>
)
}
}