/
beck
/
gsap_mac
Обзор
Документация
Войти
/
beck
/
gsap_mac
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
features
src/components/Features.tsx
139 строк
3 KB
yuldoshev.ihtier
feat(ui): implement features section
08 ноя 2025, 13:23
Не верифицирован
08 ноя 2025, 13:23
9b85d97
Код
Авторство
О чём код?
import { Canvas } from '@react-three/fiber' import { useMediaQuery } from 'react-responsive' import { Html } from '@react-three/drei' import { useGSAP } from '@gsap/react' import gsap from 'gsap' import clsx from 'clsx' import { Suspense, useEffect, useRef } from 'react' import type { Group, Object3DEventMap } from 'three' import { StudioLights } from './three/StudioLights' import { FEATURE_SEQUENCE, FEATURES, SCALE_DEFAULT_LARGE_DESKTOP, SCALE_DEFAULT_SMALL_DESKTOP, } from '../consts' import { MacbookModel } from './models/Macbook' import { useMackbookStore } from '../store' const ModelScroll = () => { const groupRef = useRef<Group<Object3DEventMap>>(null) const isMobile = useMediaQuery({ query: '(max-width: 1024px)' }) const { setTexture } = useMackbookStore() // Pre-load all feature videos during component mount useEffect(() => { FEATURE_SEQUENCE.forEach((feature) => { const v = document.createElement('video') Object.assign(v, { src: feature.videoPath, muted: true, playsInline: true, preload: 'auto', crossOrigin: 'anonymous', }) v.load() }) }, []) useGSAP(() => { // 3D MODEL ROTATION ANIMATION const modelTimeline = gsap.timeline({ scrollTrigger: { trigger: '#f-canvas', start: 'top top', end: 'bottom top', scrub: 1, pin: true, }, }) // SYNC THE FEATURE CONTENT const timeline = gsap.timeline({ scrollTrigger: { trigger: '#f-canvas', start: 'top center', end: 'bottom top', scrub: 1, }, }) // 3D SPIN if (groupRef.current) { modelTimeline.to(groupRef.current.rotation, { y: Math.PI * 2, ease: 'power1.inOut', }) } // Content & Texture Sync timeline .call(() => setTexture('/videos/feature-1.mp4')) .to('.box1', { opacity: 1, y: 0, delay: 1 }) .call(() => setTexture('/videos/feature-2.mp4')) .to('.box2', { opacity: 1, y: 0 }) .call(() => setTexture('/videos/feature-3.mp4')) .to('.box3', { opacity: 1, y: 0 }) .call(() => setTexture('/videos/feature-4.mp4')) .to('.box4', { opacity: 1, y: 0 }) .call(() => setTexture('/videos/feature-5.mp4')) .to('.box5', { opacity: 1, y: 0 }) }, []) return ( <group ref={groupRef}> <Suspense fallback={ <Html> <h1 className="text-white text-3xl uppercase">Loading...</h1> </Html> } > <MacbookModel scale={ isMobile ? SCALE_DEFAULT_SMALL_DESKTOP : SCALE_DEFAULT_LARGE_DESKTOP } position={[0, -1, 0]} /> </Suspense> </group> ) } export const Features = () => { return ( <section id="features"> <h2>See it all in a new light.</h2> <Canvas id="f-canvas" camera={{}}> <StudioLights /> <ambientLight intensity={0.5} /> <ModelScroll /> </Canvas> <div className="absolute inset-0"> {FEATURES.map((feature, index) => ( <div key={feature.id} className={clsx('box', `box${index + 1}`, feature.styles)} > <img src={feature.icon} alt={feature.highlight} /> <p> <span className="text-white">{feature.highlight}</span> {feature.text} </p> </div> ))} </div> </section> ) }