Как анимировать прямоугольники с помощью r3f в React?

У меня возникла проблема с правильной анимацией имеющейся у меня glb модели, но, к сожалению, я не так хорош в React-Three-Fiber.

Анимация, которую я пытаюсь создать, — это фоновая карусель (или я бы назвал ее эффектом змеи), которая доступна на сайте Brainsave.ai

Я пытался использовать функцию Math.sin, чтобы получить аналогичный результат, но что бы я ни делал, это не сработает.

У меня есть несколько переменных:

    const [scrollOffset, setScrollOffset] = useState(0);
    const numRectangles = 20;
    const amplitude = 1; 
    const frequency = 0.4; 
    const spacing = 0.3; 
    const speed = 0.005; 

и используя функцию прокрутки, я пытаюсь установить смещение:

    const handleScroll = (event) => {
        setScrollOffset((prev) => prev + event.deltaY * speed);
    };

и в моем возврате jsx я заполняю эти прямоугольники и использую координаты x и y, устанавливая положение прямоугольников.

 return (
        <div className = {styles.card} onWheel = {handleScroll} style = {{ height: '100vh', overflow: 'hidden' }}>
            <Canvas>
                <ambientLight intensity = {0.5} />
                <spotLight position = {[10, 10, 10]} angle = {0.15} penumbra = {1} intensity = {1} />
                <pointLight position = {[-10, -10, -10]} intensity = {1} />

                {Array.from({ length: numRectangles }).map((_, index) => {
                    // Positioning based on a sine wave pattern
                    const x = -index * spacing;
                    const y = amplitude * Math.sin(frequency * (x - scrollOffset));
                    
                    // Rotation based on the index, creating a gradual rotation
                    const rotation = [4, -10, 0]; // Rotate slightly around the z-axis

                    return <Model key = {index} position = {[x, y, 0]} rotation = {rotation} />;
                })}
            </Canvas>
        </div>
    );

Также прилагаю коды и коробку , над которыми я работаю, чтобы вы могли запустить проект.

Однако анимация — это не то, чего я хочу достичь, и я не могу этого сделать. Был бы признателен, если бы кто-нибудь мог с этим помочь.

Приятного кодирования!

Хотите объяснить, почему минусуют?

Nijat Mursali 16.08.2024 17:06
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
1
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Возможно, стоит начать с этой отправной точки и адаптироваться к ней в проекте. Измените значения в Card, и вы найдете оптимальную настройку.

Взгляните на это и это и это тоже.

export default function Card() {
  const [scrollOffset, setScrollOffset] = useState(0);
  const numRectangles = 60;
  const radius = 2;
  const height = 7;
  //const turns = 5;
  const speed = 0.005;

  const handleScroll = (event) => {
    setScrollOffset((prev) => prev + event.deltaY * speed);
  };

  return (
    <div
      className = {styles.card}
      onWheel = {handleScroll}
      style = {{ height: "100vh", overflow: "hidden" }}
    >
      <Canvas>
        <ambientLight intensity = {0.5} />
        <spotLight
          position = {[10, 10, 10]}
          angle = {0.15}
          penumbra = {1}
          intensity = {1}
        />
        <pointLight position = {[-10, -10, -10]} intensity = {1} />

        {Array.from({ length: numRectangles }).map((_, index) => {
          const angle = index * 0.2;
          const x = radius * Math.cos(angle);
          const z = radius * Math.sin(angle);
          const y = (index / numRectangles) * height - height / 2;
          const rotation = [0, 0, 0];

          return (
            <Model
              key = {index}
              position = {[x, y - scrollOffset, z]}
              rotation = {rotation}
            />
          );
        })}
      </Canvas>
    </div>
  );
}

Если вы хотите добавить анимацию вдоль пути, вы можете использовать progress вместо angle.

<Canvas>
                <ambientLight intensity = {0.5} />
                <spotLight position = {[10, 10, 10]} angle = {0.15} penumbra = {1} intensity = {1} />
                <pointLight position = {[-10, -10, -10]} intensity = {1} />

                {Array.from({ length: numRectangles }).map((_, index) => {
                    const progress = (index + scrollOffset) * 0.2; // 
                    const x = radius * Math.cos(progress);         
                    const z = radius * Math.sin(progress);       
                    const y = (index / numRectangles) * height - height / 2;

                    const rotation = [0, 0, 0];

                    return <Model key = {index} position = {[x, y, z]} rotation = {rotation} />;
                })}
</Canvas>

ОБНОВЛЯТЬ

Чтобы создать впечатление выхода из глубины (или, назовем это, из оси Z), добавьте zDepth. Чтобы добавить плавную прокрутку, вы можете использовать функцию интерполяции lerp. Вы также можете просто использовать помощника Drei ScrollControl .

import React, { useRef, useState, useEffect } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import styles from "./styles.module.css";
import { useGLTF, Html } from "@react-three/drei";

// function RedRectangleHelper() {
//   const rectangleStyle = {
//     width: "450px",
//     height: "250px",
//     border: "2px solid red",
//     position: "absolute",
//   };

//   return (
//     <Html position = {[-3.5, 1.5, 0]}>
//       <div style = {rectangleStyle}></div>
//     </Html>
//   );
// }

function Model({ position }) {
  const { nodes, materials } = useGLTF("/timeline-rect.glb");
  return (
    <group position = {position} dispose = {null}>
      <mesh
        geometry = {nodes.Plane_1.geometry}
        material = {materials["Material.002"]}
      />
      <mesh
        geometry = {nodes.Plane_2.geometry}
        material = {materials["Material.001"]}
      />
    </group>
  );
}

function ScrollingPlanes({ scrollOffset }) {
  const numRectangles = 80;
  const radius = 6;
  const height = 3;

  return (
    <>
      {Array.from({ length: numRectangles }).map((_, index) => {
        const progress = (index + scrollOffset) * 0.11;

        const x = -radius * Math.sin(progress);
        const z = -radius * Math.sin(progress);
        const y = (index / numRectangles) * height - height / 2;

        const zDepth = -index * 0.5;

        const rotation = [0, 0, 0];

        return (
          <Model
            key = {index}
            position = {[x, y, z + zDepth]}
            rotation = {rotation}
          />
        );
      })}
    </>
  );
}

function ScrollSmooth({ setScrollOffset }) {
  const targetScrollOffset = useRef(0);

  useFrame(() => {
    setScrollOffset((prev) => {
      const lerp = (a, b, t) => a + (b - a) * t;
      return lerp(prev, targetScrollOffset.current, 0.1);
    });
  });

  const handleScroll = (event) => {
    targetScrollOffset.current += -event.deltaY * 0.005; //invert delta to creating direction of moving planes
  };

  useEffect(() => {
    window.addEventListener("wheel", handleScroll, { passive: true });
    return () => window.removeEventListener("wheel", handleScroll);
  }, []);

  return null;
}

export default function Card() {
  const [scrollOffset, setScrollOffset] = useState(0);

  return (
    <div
      className = {styles.card}
      style = {{ height: "100vh", overflow: "hidden" }}
    >
      <Canvas>
        <ambientLight intensity = {0.5} />
        <spotLight
          position = {[10, 10, 10]}
          angle = {0.15}
          penumbra = {1}
          intensity = {1}
        />
        <pointLight position = {[-10, -10, -10]} intensity = {1} />

        <ScrollSmooth setScrollOffset = {setScrollOffset} />
        <ScrollingPlanes scrollOffset = {scrollOffset} />
      </Canvas>
    </div>
  );
}

ПЕСОЧНИЦА

Спасибо за ответ @Łukasz D. Mastalerz. Я бы сказал, что второй вариант намного лучше, поскольку progress может выполнить всю работу, но, глядя на результат, я думаю, что rotation неверно, не так ли? Вы можете проверить результат в кодесанбоксе в вопросе. Я не уверен, что дело в расстоянии. или поворот, отличающийся от эталонного.

Nijat Mursali 15.08.2024 23:59

@NijatMursali Проверьте обновление, пожалуйста.

Łukasz D. Mastalerz 16.08.2024 12:18

Я очень ценю время и усилия, которые вы потратили на этот вопрос! Я бы сказал, что это действительно решает проблему, а также дает мне больше идей немного ее подправить. Я принял ответ! Спасибо и хорошего дня.

Nijat Mursali 16.08.2024 12:44

Другие вопросы по теме