返回

用 React 实现图像扭曲效果,让你的网站动起来!

javascript

用 React 为图像增添活力:打造令人惊叹的扭曲效果

简介

在当今数字世界中,视觉效果在吸引用户并让他们沉浸在体验中方面发挥着至关重要的作用。图像扭曲效果是一种令人惊叹的技术,它可以为你的网站或应用程序添加动感和互动性。本文将指导你如何使用 React 和 "react-curtains" 库在你的项目中实现这种迷人的效果。

认识 "react-curtains"

"react-curtains" 是一个 React 库,它提供了用于创建 3D 图形和效果的强大工具。它与 Three.js 集成,后者是一个流行的 JavaScript 3D 库,它使你在 React 应用程序中轻松实现复杂的效果。

创建画布

开始创建扭曲效果的第一步是创建画布元素。这可以轻松地使用 "react-curtains" 库来实现。画布将充当图像的容器,并允许你应用扭曲效果。

import { useCurtains } from "react-curtains";

const Canvas = () => {
  const { Renderer, Plane } = useCurtains();
  return (
    <Renderer>
      <Plane position={[0, 0, -1]} scale={[1, 1, 1]} anchor={[0.5, 0.5]} />
    </Renderer>
  );
};

应用扭曲效果

有了画布之后,就可以应用扭曲效果了。扭曲效果是通过操纵图像顶点的位置来实现的。为此,需要编写一个定制的顶点着色器。

const vertexShader = `
      attribute vec3 aVertexPosition;
      attribute vec2 aVertexUV;
      uniform mat4 uProjectionMatrix;
      uniform mat4 uViewMatrix;
      uniform mat4 uModelMatrix;
      uniform vec2 uMouse;
      varying vec2 vUv;

      void main() {
        vec3 pos = aVertexPosition;
        pos.x += sin(uMouse.x * 10.0) * 0.05;
        pos.y += sin(uMouse.y * 10.0) * 0.05;

        gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(pos, 1.0);
        vUv = aVertexUV;
      }
    `;

在顶点着色器中,鼠标位置作为统一变量传递,并用于计算每个顶点的新位置。这将根据鼠标移动来扭曲图像。

响应鼠标移动

为了响应鼠标移动,需要在鼠标移动时更新鼠标位置。这可以使用事件监听器来实现。

const mouse = { x: 0, y: 0 };
window.addEventListener("mousemove", (e) => {
  mouse.x = e.clientX / window.innerWidth;
  mouse.y = e.clientY / window.innerHeight;
});

渲染效果

最后,在渲染循环中,需要更新鼠标位置并渲染场景。

Plane.onRender(() => {
  Plane.material.uniforms.mouse.value = mouse;
});

示例代码

以下示例代码演示了如何使用 "react-curtains" 创建图像扭曲效果:

import { useEffect, useRef } from "react";
import { useCurtains } from "react-curtains";

const Canvas = () => {
  const { Renderer, Plane } = useCurtains();
  const canvasRef = useRef(null);
  const mouse = { x: 0, y: 0 };

  useEffect(() => {
    const canvas = canvasRef.current;
    const gl = canvas.getContext("webgl");

    const vertexShader = `
      attribute vec3 aVertexPosition;
      attribute vec2 aVertexUV;
      uniform mat4 uProjectionMatrix;
      uniform mat4 uViewMatrix;
      uniform mat4 uModelMatrix;
      uniform vec2 uMouse;
      varying vec2 vUv;

      void main() {
        vec3 pos = aVertexPosition;
        pos.x += sin(uMouse.x * 10.0) * 0.05;
        pos.y += sin(uMouse.y * 10.0) * 0.05;

        gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(pos, 1.0);
        vUv = aVertexUV;
      }
    `;

    const fragmentShader = `
      precision highp float;
      uniform sampler2D uTexture;
      varying vec2 vUv;

      void main() {
        gl_FragColor = texture2D(uTexture, vUv);
      }
    `;

    const program = createProgram(gl, vertexShader, fragmentShader);

    const positionAttributeLocation = gl.getAttribLocation(program, "aVertexPosition");
    const uvAttributeLocation = gl.getAttribLocation(program, "aVertexUV");
    const projectionMatrixUniformLocation = gl.getUniformLocation(program, "uProjectionMatrix");
    const viewMatrixUniformLocation = gl.getUniformLocation(program, "uViewMatrix");
    const modelMatrixUniformLocation = gl.getUniformLocation(program, "uModelMatrix");
    const mouseUniformLocation = gl.getUniformLocation(program, "uMouse");

    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = new Float32Array([
      -1.0, -1.0, 0.0,
       1.0, -1.0, 0.0,
       1.0,  1.0, 0.0,
      -1.0,  1.0, 0.0,
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

    const uvBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
    const uvs = new Float32Array([
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);

    const indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    const indices = new Uint16Array([
      0, 1, 2,
      0, 2, 3,
    ]);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(program);

    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(uvAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
    gl.vertexAttribPointer(uvAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    gl.uniformMatrix4fv(projectionMatrixUniformLocation, false, glMatrix.mat4.ortho(-1, 1, -1, 1, -1, 1));
    gl.uniformMatrix4fv(viewMatrixUniformLocation, false, glMatrix.mat4.identity());
    gl.uniformMatrix4fv(modelMatrixUniformLocation, false, glMatrix.mat4.identity());
    gl.uniform2fv(mouseUniformLocation, [0.0, 0.0]);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

    window.addEventListener("mousemove", (e) => {
      mouse.x = e.clientX / gl.canvas.width;
      mouse.y = e.clientY / gl.canvas.height;
    });

    function animate() {
      requestAnimationFrame(animate);

      gl.clear(gl.COLOR_BUFFER_BIT);

      gl.uniform2fv(mouseUniformLocation, [mouse.x, mouse.y]);

      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    }

    animate();
  }, []);

  return <Renderer ref={canvasRef} style={{ width: "100vw", height: "100vh" }} />;
};

export default Canvas;

结论

在 React 中创建图像扭曲效果是一种增强用户交互并创造引人入胜体验的有效方式。"react-curtains" 库提供了方便的工具,使你能够轻松地实现这种效果。