video-flow-b/components/parallax.tsx
2025-07-01 17:00:02 +08:00

232 lines
5.8 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import dynamic from 'next/dynamic';
// 导入图片资源
import mono from '@/assets/3dr_mono.png';
import chihiro from '@/assets/3dr_chihiro.png';
import howlcastle from '@/assets/3dr_howlcastle.png';
import monobg from '@/assets/3dr_monobg.jpg';
import spirited from '@/assets/3dr_spirited.jpg';
import howlbg from '@/assets/3dr_howlbg.jpg';
const Parallax = () => {
const pageXRef = useRef<HTMLDivElement>(null);
const cardsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const pageX = pageXRef.current;
const cards = cardsRef.current;
const images = document.querySelectorAll('.card__img') as NodeListOf<HTMLImageElement>;
const backgrounds = document.querySelectorAll('.card__bg') as NodeListOf<HTMLDivElement>;
let timeout: number | undefined;
const range = 40;
// 计算旋转角度
const calcValue = (a: number, b: number): string => ((a / b) * range - range / 2).toFixed(1);
// 视差动画函数
const parallax = (e: MouseEvent): void => {
const x = e.clientX;
const y = e.clientY;
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => {
const xValue = calcValue(x, window.innerWidth);
const yValue = calcValue(y, window.innerHeight);
// 设置卡片容器的旋转角度
if (cards) {
cards.style.transform = `rotateX(${yValue}deg) rotateY(${xValue}deg)`;
}
// 设置所有图片的位移
images.forEach(item => {
item.style.transform = `translateX(${-xValue}px) translateY(${yValue}px)`;
});
// 设置所有背景的位置
backgrounds.forEach(item => {
item.style.backgroundPosition = `${Number(xValue) * 0.45}px ${-Number(yValue) * 0.45}px`;
});
});
};
if (pageX) {
// Add the passive flag to the event listener to improve scrolling performance
pageX.addEventListener('mousemove', parallax as EventListener, { passive: true });
return () => {
pageX.removeEventListener('mousemove', parallax as EventListener);
};
}
return undefined;
}, []);
return (
<PageX ref={pageXRef}>
<Cards ref={cardsRef}>
<h3>Movies</h3>
<h1>Popular</h1>
{/* 幽灵公主 */}
<Card className="princess-mononoke">
<CardBg className="card__bg" bgUrl={monobg.src} />
<CardImg className="card__img" src={mono.src} alt="Princess Mononoke" />
<CardText>
<CardTitle>Princess Mononoke</CardTitle>
</CardText>
</Card>
{/* 千与千寻 */}
<Card className="spirited-away">
<CardBg className="card__bg" bgUrl={spirited.src} />
<CardImg className="card__img" src={chihiro.src} alt="Spirited Away" />
<CardText>
<CardTitle>Spirited Away</CardTitle>
</CardText>
</Card>
{/* 哈尔的移动城堡 */}
<Card className="howl-s-moving-castle">
<CardBg className="card__bg" bgUrl={howlbg.src} />
<CardImg className="card__img" src={howlcastle.src} alt="Howl's Moving Castle" />
<CardText>
<CardTitle>Howl's Moving Castle</CardTitle>
</CardText>
</Card>
</Cards>
</PageX>
);
};
// 样式定义
const PageX = styled.div`
width: 1000px;
height: 700px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
perspective: 1800px;
background: #642b73;
background: linear-gradient(to bottom, #c6426e, #642b73);
`;
const Cards = styled.div`
display: inline-block;
min-width: 595px;
padding: 30px 35px;
perspective: 1800px;
transform-origin: 50% 50%;
transform-style: preserve-3d;
border-radius: 15px;
text-align: left;
background: #fff;
box-shadow: 0px 10px 20px 20px rgba(0, 0, 0, 0.17);
h1 {
margin-bottom: 30px;
transform: translateZ(35px);
letter-spacing: -1px;
font-size: 32px;
font-weight: 800;
color: #3e3e42;
}
h3 {
margin-bottom: 6px;
transform: translateZ(25px);
font-size: 16px;
color: #eb285d;
}
`;
const Card = styled.div`
display: inline-block;
width: 175px;
height: 250px;
position: relative;
overflow: hidden;
perspective: 1200px;
transform-style: preserve-3d;
transform: translatez(35px);
transition: transform 200ms ease-out;
text-align: center;
border-radius: 15px;
box-shadow: 5px 5px 20px -5px rgba(0, 0, 0, 0.6);
&:not(:last-child) {
margin-right: 30px;
}
&.princess-mononoke .card__img {
top: 14px;
right: -10px;
height: 110%;
}
&.spirited-away .card__img {
top: 25px;
}
&.howl-s-moving-castle .card__img {
top: 5px;
left: -4px;
height: 110%;
}
`;
interface CardBgProps {
bgUrl: string;
}
const CardBg = styled.div<CardBgProps>`
bottom: -50px;
left: -50px;
position: absolute;
right: -50px;
top: -50px;
transform-origin: 50% 50%;
transform: translateZ(-50px);
z-index: 0;
background: ${props => `url(${props.bgUrl}) center/cover no-repeat`};
`;
const CardImg = styled.img`
position: relative;
height: 100%;
`;
const CardText = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 70px;
position: absolute;
z-index: 2;
bottom: 0;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.55) 100%
);
`;
const CardTitle = styled.p`
margin-bottom: 3px;
padding: 0 10px;
font-size: 18px;
font-weight: 700;
color: #fff;
`;
// Export with noSSR to prevent server-side rendering issues
export default dynamic(() => Promise.resolve(Parallax), { ssr: false });