У меня возникла проблема с правильной анимацией имеющейся у меня 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>
);
Также прилагаю коды и коробку , над которыми я работаю, чтобы вы могли запустить проект.
Однако анимация — это не то, чего я хочу достичь, и я не могу этого сделать. Был бы признателен, если бы кто-нибудь мог с этим помочь.
Приятного кодирования!





Возможно, стоит начать с этой отправной точки и адаптироваться к ней в проекте. Измените значения в 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 неверно, не так ли? Вы можете проверить результат в кодесанбоксе в вопросе. Я не уверен, что дело в расстоянии. или поворот, отличающийся от эталонного.
@NijatMursali Проверьте обновление, пожалуйста.
Я очень ценю время и усилия, которые вы потратили на этот вопрос! Я бы сказал, что это действительно решает проблему, а также дает мне больше идей немного ее подправить. Я принял ответ! Спасибо и хорошего дня.
Хотите объяснить, почему минусуют?