akella / webgl-mouseover-effects

Demos for the tutorial on how to achieve an interactive mouseover/hover effect
https://tympanus.net/codrops/?p=49069
MIT License
346 stars 61 forks source link

Implementing in React gives errors.. #4

Closed Arshazar closed 3 years ago

Arshazar commented 3 years ago

I wanted to use your code in react project, I changed somethings, but it ain't working....

this is the changed scene.js :

import * as THREE from "three"
import * as dat from "dat.gui"
import EffectComposer, {
    Pass,
    RenderPass,
    ShaderPass,
    TexturePass,
    ClearPass,
    MaskPass,
    ClearMaskPass,
    CopyShader,
} from '@johh/three-effectcomposer'
let OrbitControls = require("three-orbit-controls")(THREE)

const createInputEvents = require('simple-input-events');
const event = createInputEvents(window);

const clamp = (min, max) => (value) =>
    value < min ? min : value > max ? max : value;

export function Scene (container) {

  let scene = new THREE.Scene();

  let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true})
  let width = window.innerWidth
  let height = window.innerHeight
  // this.renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setPixelRatio(1)
  renderer.setSize(width, height)
  renderer.sortObjects = false

  renderer.outputEncoding = THREE.sRGBEncoding;

  container.appendChild(renderer.domElement)

  let camera = new THREE.PerspectiveCamera(
      70,
      width / height,
      100,
      1000
  )

  let cameraDistance = 400
  camera.position.set(0, 0, cameraDistance)
  camera.lookAt(0, 0, 0)
  let time = 0
  let speed = 0
  let targetSpeed = 0
  let mouse = new THREE.Vector2()
  let followMouse = new THREE.Vector2()
  let prevMouse = new THREE.Vector2()

  let paused = false
  let controls = new OrbitControls(camera, renderer.domElement)

  let that = this;
  let settings = {
    velo: 0,
    scale: 0,
    colorful: ()=>{
      // that.makeColorful()
      that.customPass.uniforms.uType.value = 0
    },
    zoom: ()=>{
      that.customPass.uniforms.uType.value = 1
    },
    random: ()=>{
      that.customPass.uniforms.uType.value = 2
    },
  };
  let gui = new dat.GUI()
  // this.gui.add(this.settings, "progress", -1, 2, 0.01);
  // this.gui.add(this.settings, "velo", 0, 1, 0.01);
  // this.gui.add(this.settings, "scale", 0, 1, 0.01);
  gui.add(settings, "colorful")
  gui.add(settings, "zoom")
  gui.add(settings, "random")

  window.addEventListener("resize", resize.bind(this))

  let composer = new EffectComposer(renderer)
  let renderPass = new RenderPass(scene, camera)
  composer.addPass(renderPass)

  //custom shader pass
  let counter = 0.0
  let myEffect = {
    uniforms: {
      "tDiffuse": { value: null },
      "distort": { value: 0 },
      "resolution": { value: new THREE.Vector2(1.,window.innerHeight/window.innerWidth) },
      "uMouse": { value: new THREE.Vector2(-10,-10) },
      "uVelo": { value: 0 },
      "uScale": { value: 0 },
      "uType": { value: 0 },
      "time": { value: 0 }
    },
    vertexShader: `
      uniform float time;
      uniform float progress;
      uniform vec2 resolution;
      varying vec2 vUv;
      uniform sampler2D texture1;

      const float pi = 3.1415925;

      void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );
      }`,
    fragmentShader: `
      uniform float time;
      uniform float progress;
      uniform sampler2D tDiffuse;
      uniform vec2 resolution;
      varying vec2 vUv;
      uniform vec2 uMouse;
      uniform float uVelo;
      uniform int uType;

      float circle(vec2 uv, vec2 disc_center, float disc_radius, float border_size) {
      uv -= disc_center;
      uv*=resolution;
      float dist = sqrt(dot(uv, uv));
      return smoothstep(disc_radius+border_size, disc_radius-border_size, dist);
      }

      float map(float value, float min1, float max1, float min2, float max2) {
      return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
      }

      float remap(float value, float inMin, float inMax, float outMin, float outMax) {
        return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
      }

      float hash12(vec2 p) {
      float h = dot(p,vec2(127.1,311.7));
      return fract(sin(h)*43758.5453123);
      }

      // #define HASHSCALE3 vec3(.1031, .1030, .0973)
      vec2 hash2d(vec2 p)
      {
      vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
          p3 += dot(p3, p3.yzx+19.19);
          return fract((p3.xx+p3.yz)*p3.zy);
      }

      void main(){
      vec2 newUV = vUv;
      vec4 color = vec4(1.,0.,0.,1.);

      // colorful
      if(uType==0){
      float c = circle(newUV, uMouse, 0.0, 0.2);
      float r = texture2D(tDiffuse, newUV.xy += c * (uVelo * .5)).x;
      float g = texture2D(tDiffuse, newUV.xy += c * (uVelo * .525)).y;
      float b = texture2D(tDiffuse, newUV.xy += c * (uVelo * .55)).z;
      color = vec4(r, g, b, 1.);
      }

      // zoom
      if(uType==1){
      float c = circle(newUV, uMouse, 0.0, 0.1+uVelo*2.)*40.*uVelo;
      vec2 offsetVector = normalize(uMouse - vUv);
      vec2 warpedUV = mix(vUv, uMouse, c * 0.99); //power
      color = texture2D(tDiffuse,warpedUV) + texture2D(tDiffuse,warpedUV)*vec4(vec3(c),1.);
      }

      // zoom
      if(uType==2){
      float hash = hash12(vUv*10.);
      // float c = -circle(newUV, uMouse, 0.0, 0.1+uVelo*2.)*40.*uVelo;
      // vec2 offsetVector = -normalize(uMouse - vUv);
      // vec2 warpedUV = mix(vUv, uMouse, c * 0.6); //power
      // vec2 warpedUV1 = mix(vUv, uMouse, c * 0.3); //power
      // vec2 warpedUV2 = mix(vUv, uMouse, c * 0.1); //power
      // color = vec4(
      // texture2D(tDiffuse,warpedUV ).r,
      // texture2D(tDiffuse,warpedUV1 ).g,
      // texture2D(tDiffuse,warpedUV2 ).b,
      // 1.);
      // color = vec4(,0.,0.,1.);
      float c = circle(newUV, uMouse, 0.0, 0.1+uVelo*0.01)*10.*uVelo;
      vec2 offsetVector = normalize(uMouse - vUv);
      // vec2 warpedUV = mix(vUv, uMouse,  20.*hash*c); //power
      vec2 warpedUV = vUv + vec2(hash - 0.5)*c; //power
      color = texture2D(tDiffuse,warpedUV) + texture2D(tDiffuse,warpedUV)*vec4(vec3(c),1.);
      }

      gl_FragColor = color;
      }
    `
  }

  let customPass = new ShaderPass(myEffect)
  customPass.renderToScreen = true
  composer.addPass(customPass)

  event.on('move', ({ position, event, inside, dragging }) => {
    // mousemove / touchmove
    mouse.x = (position[0] / width)
    mouse.y = 1. - (position[1]/ height)
  })

  let geometry = new THREE.PlaneBufferGeometry(1, 1, 80, 80)
  let material = new THREE.ShaderMaterial({
    extensions: {
      derivatives: "#extension GL_OES_standard_derivatives : enable"
    },
    side: THREE.DoubleSide,
    uniforms: {
      time: { type: "f", value: 0 },
      progress: { type: "f", value: 0 },
      angle: { type: "f", value: 0 },
      texture1: { type: "t", value: null },
      texture2: { type: "t", value: null },
      resolution: { type: "v4", value: new THREE.Vector4() },
      uvRate1: {
        value: new THREE.Vector2(1, 1)
      }
    },
    // wireframe: true,
    transparent: true,
    vertexShader: `
      uniform float time;
      uniform float progress;
      uniform vec4 resolution;
      varying vec2 vUv;
      uniform sampler2D texture1;

      const float pi = 3.1415925;

      void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );
      }`,
    fragmentShader: `
      uniform float time;
      uniform float progress;
      uniform sampler2D texture1;
      uniform vec4 resolution;
      varying vec2 vUv;

      void main(){
      vec2 newUV = (vUv - vec2(0.5))*resolution.zw + vec2(0.5);
      // newUV.x += 0.02*sin(newUV.y*20. + time);
      gl_FragColor = texture2D(texture1,newUV);
      }
    `
  })

  function resize() {
    renderer.setSize(width, height)
    camera.aspect = width / height

    camera.fov = 2 * Math.atan(width / camera.aspect / (2 * cameraDistance)) * (180 / Math.PI); // in degrees
    customPass.uniforms.resolution.value.y = height / width
    camera.updateProjectionMatrix();
  }

  resize()

  function render() {
    time+=0.05
    getSpeed()
    scene.children.forEach(m => {
      if (m.material.uniforms) {
        m.material.uniforms.angle.value = settings.angle
        m.material.uniforms.time.value = time
      }
    });
    customPass.uniforms.time.value = time
    customPass.uniforms.uMouse.value = followMouse
    // this.customPass.uniforms.uVelo.value = this.settings.velo;
    customPass.uniforms.uVelo.value = Math.min(targetSpeed, 0.05);
    targetSpeed *=0.999;
    // this.renderer.render(this.scene, this.camera);
    if(composer) composer.render()
  }

  render()

  function createMesh(o) {
    let material = this.material.clone()
    let texture = new THREE.Texture(o.image)
    texture.needsUpdate = true
    // image cover
    let imageAspect = o.iHeight / o.iWidth
    let a1
    let a2
    if (o.height / o.width > imageAspect) {
      a1 = (o.width / o.height) * imageAspect
      a2 = 1
    } else {
      a1 = 1
      a2 = o.height / o.width / imageAspect
    }
    texture.minFilter = THREE.LinearFilter
    material.uniforms.resolution.value.x = o.width
    material.uniforms.resolution.value.y = o.height
    material.uniforms.resolution.value.z = a1
    material.uniforms.resolution.value.w = a2
    material.uniforms.progress.value = 0
    material.uniforms.angle.value = 0.3

    material.uniforms.texture1.value = texture
    material.uniforms.texture1.value.needsUpdate = true

    let mesh = new THREE.Mesh(geometry, material)

    mesh.scale.set(o.width, o.height, o.width / 2)

    return mesh
  }

  function stop() {
    paused = true;
  }

  function play() {
    paused = false
    render()
  }

  function getSpeed(){
    speed = Math.sqrt( (prevMouse.x- mouse.x)**2 + (prevMouse.y- mouse.y)**2 );

    targetSpeed -= 0.1*(targetSpeed - this.speed);
    followMouse.x -= 0.1*(followMouse.x - mouse.x);
    followMouse.y -= 0.1*(followMouse.y - mouse.y);

    prevMouse.x = mouse.x;
    prevMouse.y = mouse.y;
  }

  return {
    createMesh: createMesh,
    scene: scene,
    render: render,
    targetSpeed: targetSpeed
  }
}

this is the changed app.js:

import imagesLoaded from "imagesloaded"
import {Scene} from "./scene"

let scene
// helper functions
const MathUtils = {
  // map number x from range [a, b] to [c, d]
  map: (x, a, b, c, d) => ((x - a) * (d - c)) / (b - a) + c,
  // linear interpolation
  lerp: (a, b, n) => (1 - n) * a + n * b
};

// body element
const wrapper = document.querySelector('.pic-wrapper')
let IMAGES

// calculate the viewport size
let winsize;
const calcWinsize = () =>
  (winsize = { width: window.innerWidth, height: window.innerHeight });
calcWinsize();
// and recalculate on resize
window.addEventListener("resize", calcWinsize);

window.onbeforeunload = function() {
  window.scrollTo(0, 0);
};

// scroll position and update function
let docScroll;
const getPageYScroll = () =>
  (docScroll = window.pageYOffset || document.documentElement.scrollTop)
window.addEventListener("scroll", getPageYScroll)

// Item
function Item (el, scroll) {
  let DOM = { el: el.img }
  let currentScroll = docScroll
  let animated = false
  let isBeingAnimatedNow = false
  let shouldRollBack = false
  let shouldUnRoll = false
  let positions = []

  const bounds = DOM.el.getBoundingClientRect()
  const fromTop = bounds.top
  const windowHeight = window.innerHeight
  const withoutHeight = fromTop - windowHeight
  const withHeight = fromTop + bounds.height
  let insideTop = withoutHeight - docScroll
  let insideRealTop = fromTop + docScroll
  let insideBottom = withHeight - docScroll + 50
  let width = bounds.width
  let height = bounds.height
  let left = bounds.left
  let src = document.getElementById('picture')

  let mesh = scene.createMesh({
    width: width,
    height: height,
    src: src,
    image: DOM.el,
    iWidth: DOM.el.width,
    iHeight: DOM.el.height
  })
  scene.scene.add(mesh)
  // use the IntersectionObserver API to check when the element is inside the viewport
  // only then the element translation will be updated
  let options = {
    root: null,
    rootMargin: "0px",
    threshold: [0, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
  }
  let observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      positions.push(entry.boundingClientRect.y)
      let compareArray = this.positions.slice(
          positions.length - 2,
          positions.length
      );
      let down = compareArray[0] > compareArray[1]

      let isVisible = entry.intersectionRatio > 0.0

      let shouldRollBack = false
      let shouldUnRoll = false
      if (
          entry.intersectionRatio < 0.5 &&
          entry.boundingClientRect.y > 0 &&
          isVisible &&
          !down
      ) {
        shouldRollBack = true
      }

      if (
          entry.intersectionRatio > 0.5 &&
          entry.boundingClientRect.y > 0 &&
          isVisible
      ) {
        shouldUnRoll = true
      }
      console.log(isVisible, 'vis')
      mesh.visible = isVisible
    })
  }, options)
  observer.observe(DOM.el)

  function resize() {
    // on resize rest sizes and update the translation value
    mesh.scale.set(width, height, 200)
    render(scroll.renderedStyles.translationY.current)
    scroll.shouldRender = true
  }
  // init/bind events
  window.addEventListener("resize", () => resize())

  function render(s) {
    currentScroll = s
    mesh.position.y = currentScroll + winsize.height / 2 - insideRealTop - height / 2
    mesh.position.x = 0 - winsize.width / 2 + left + width / 2
  }
  render(0)
}

function SmoothScroll() {
  let shouldRender = false
  // the <main> element
  let DOM = { main: document.querySelector(".pic-main") };
  // the scrollable element
  // we translate this element when scrolling (y-axis)
  DOM.scrollable = DOM.main.querySelector(".scrollable");
  // the items on the page
  let items = [];

  IMAGES.forEach(image => {
    if (image.img.classList.contains("js-image")) {
      items.push(new Item(image, this))
    }
  })

  document.addEventListener('mousemove',()=>{
    shouldRender = true
  })

  // here we define which property will change as we scroll the page
  // in this case we will be translating on the y-axis
  // we interpolate between the previous and current value to achieve the smooth scrolling effect
  let renderedStyles = {
    translationY: {
      // interpolated value
      previous: 0,
      // current value
      current: 0,
      // amount to interpolate
      ease: 0.1,
      // current value setter
      // in this case the value of the translation will be the same like the document scroll
      setValue: () => docScroll
    }
  }
  // set the body's height
  function setSize() {
    wrapper.style.height = `${DOM.scrollable.scrollHeight}px`
  }
  setSize()
  // set the initial values
  for (const key in renderedStyles) {
    renderedStyles[key].current = renderedStyles[key].previous = renderedStyles[key].setValue()
  }
  setPosition()
  shouldRender = true

  // translate the scrollable element
  function setPosition() {
    if (
        Math.round(renderedStyles.translationY.previous) !==
        Math.round(renderedStyles.translationY.current) ||
        renderedStyles.translationY.previous < 10
    ) {
      shouldRender = true;
      DOM.scrollable.style.transform = `translate3d(0,${-1 * renderedStyles.translationY.previous}px,0)`

      for (const item of items) {
        if (item.isVisible || item.isBeingAnimatedNow) {
          item.render(renderedStyles.translationY.previous);
        }
      }
    }
    if(scene.targetSpeed > 0.01) shouldRender = true
    if (shouldRender) {
      shouldRender = false
      scene.render()
    }
  }
  // the <main> element's style needs to be modified
  DOM.main.style.position = "fixed"
  DOM.main.style.width = DOM.main.style.height = "100%"
  DOM.main.style.top = DOM.main.style.left = 0
  DOM.main.style.overflow = "hidden"
  // init/bind events
  window.addEventListener("resize", () => setSize());
  // start the render loop
  requestAnimationFrame(() => render())

  function render() {
    // update the current and interpolated values
    for (const key in renderedStyles) {
      renderedStyles[key].current = renderedStyles[key].setValue()
      renderedStyles[key].previous = MathUtils.lerp(
          renderedStyles[key].previous,
          renderedStyles[key].current,
          renderedStyles[key].ease
      )
    }
    // and translate the scrollable element
    setPosition()

    // loop..
    requestAnimationFrame(() => render())
  }
}

/***********************************/
/********** Preload stuff **********/

export const pictureAnimation = () => {

  const preloadImages = new Promise((resolve, reject) => {
    imagesLoaded(document.getElementById("picture"), { background: true }, resolve);
  });

  preloadImages.then(images => {
    IMAGES = images.images
  })

  console.log(IMAGES)

  const preloadEverything = [preloadImages]

// And then..
  Promise.all(preloadEverything).then(() => {
    // Remove the loader
    // Get the scroll position
    getPageYScroll()
    // Initialize the Smooth Scrolling
    let container = document.querySelector(".pic-container")
    scene = new Scene(container)
    new SmoothScroll()
  })
}

I don't know what to do