Files
reactShop/src/components/ManufacturerCarousel.js

190 lines
5.2 KiB
JavaScript

import React from 'react';
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { withTranslation } from 'react-i18next';
import { withLanguage } from '../i18n/withTranslation.js';
const ITEM_WIDTH = 140 + 16; // 140px width + 16px gap
const AUTO_SCROLL_SPEED = 1.0;
class ManufacturerCarousel extends React.Component {
_isMounted = false;
originalItems = [];
animationFrame = null;
translateX = 0;
constructor(props) {
super(props);
this.state = {
items: [], // [{ id, name, src }]
};
this.carouselTrackRef = React.createRef();
}
componentDidMount() {
this._isMounted = true;
this.loadImages();
}
componentWillUnmount() {
this._isMounted = false;
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
this.animationFrame = null;
}
// Revoke object URLs to avoid memory leaks
for (const item of this.originalItems) {
if (item.src) URL.revokeObjectURL(item.src);
}
}
loadImages = () => {
window.socketManager.emit('getHerstellerImages', {}, (res) => {
if (!this._isMounted || !res?.success || !res.manufacturers?.length) return;
const items = res.manufacturers
.filter(m => m.imageBuffer)
.map(m => {
const blob = new Blob([m.imageBuffer], { type: 'image/avif' });
return { id: m.id, name: m.name || '', src: URL.createObjectURL(blob) };
})
.sort(() => Math.random() - 0.5);
if (items.length === 0) return;
this.originalItems = items;
this.setState({ items: [...items, ...items] }, () => {
this.startAutoScroll();
});
});
};
startAutoScroll = () => {
if (!this.animationFrame) {
this.animationFrame = requestAnimationFrame(this.tick);
}
};
tick = () => {
if (!this._isMounted || this.originalItems.length === 0) return;
this.translateX -= AUTO_SCROLL_SPEED;
const maxScroll = ITEM_WIDTH * this.originalItems.length;
if (Math.abs(this.translateX) >= maxScroll) {
this.translateX = 0;
}
if (this.carouselTrackRef.current) {
this.carouselTrackRef.current.style.transform = `translateX(${this.translateX}px)`;
}
this.animationFrame = requestAnimationFrame(this.tick);
};
render() {
const { t } = this.props;
const { items } = this.state;
if (!items || items.length === 0) return null;
return (
<Box sx={{ mt: 4, mb: 4 }}>
<Typography
variant="h4"
component="div"
sx={{
fontFamily: 'SwashingtonCP',
textShadow: '3px 3px 10px rgba(0, 0, 0, 0.4)',
textAlign: 'center',
mb: 2,
color: 'primary.main',
}}
>
{t('product.manufacturer')}
</Typography>
<div
style={{
position: 'relative',
overflow: 'hidden',
width: '100%',
maxWidth: '1200px',
margin: '0 auto',
padding: '0 20px',
boxSizing: 'border-box',
}}
>
{/* Fade edges */}
<div style={{
position: 'absolute', top: 0, left: 0,
width: '60px', height: '100%',
background: 'linear-gradient(to right, #c8e6c9, transparent)',
zIndex: 2, pointerEvents: 'none',
}} />
<div style={{
position: 'absolute', top: 0, right: 0,
width: '60px', height: '100%',
background: 'linear-gradient(to left, #c8e6c9, transparent)',
zIndex: 2, pointerEvents: 'none',
}} />
<div
style={{
position: 'relative',
overflow: 'hidden',
padding: '12px 0',
width: '100%',
maxWidth: '1080px',
margin: '0 auto',
boxSizing: 'border-box',
}}
>
<div
ref={this.carouselTrackRef}
style={{
display: 'flex',
gap: '16px',
alignItems: 'center',
width: 'fit-content',
transform: 'translateX(0px)',
}}
>
{items.map((item, index) => (
<div
key={`${item.id}-${index}`}
style={{
flex: '0 0 140px',
width: '140px',
height: '140px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
userSelect: 'none',
pointerEvents: 'none',
}}
>
<img
src={item.src}
alt={item.name}
draggable={false}
style={{
width: '100%',
height: '100%',
objectFit: 'contain',
display: 'block',
}}
/>
</div>
))}
</div>
</div>
</div>
</Box>
);
}
}
export default withTranslation()(withLanguage(ManufacturerCarousel));