This commit is contained in:
Raphael Elita 2025-08-21 23:53:01 +02:00
parent f3c97db5f8
commit d22ef62afb
2 changed files with 83 additions and 31 deletions

View File

@ -293,6 +293,35 @@ const RightBrand = styled.div`
}
`;
function usePreloadImages(urls: string[]) {
useEffect(() => {
if (!urls?.length) return;
const unique = Array.from(new Set(urls.filter(Boolean)));
unique.forEach((url) => {
if (!url) return;
// Проверяем, есть ли уже preload в <head>
const exists = document.querySelector<HTMLLinkElement>(
`link[rel="preload"][as="image"][href="${url}"]`
);
if (!exists) {
const link = document.createElement("link");
link.rel = "preload";
link.as = "image";
link.href = url;
document.head.appendChild(link);
}
// Дополнительно сразу грузим в memory cache
const img = new Image();
img.src = url;
});
}, [urls]);
}
/* ===================== Component ===================== */
export default function KeyBusinessSegments() {
@ -304,20 +333,19 @@ export default function KeyBusinessSegments() {
const segments = useMemo(() => mapSegments(data), [data]);
// --- предзагрузка иконок ---
useEffect(() => {
if (!segments?.length) return;
const urls = segments.flatMap((seg) =>
seg.info.flatMap((block) =>
block.items
.map((item) => item.infoLogo)
.filter((u): u is string => Boolean(u))
)
);
urls.forEach((url) => {
const img = new Image();
img.src = url;
});
// собираем все картинки для предзагрузки
const preloadUrls = useMemo(() => {
if (!segments?.length) return [];
return segments.flatMap((seg) =>
seg.info.flatMap((block) => [
block.brandLogo, // ← бренд
...block.items.map((item) => item.infoLogo), // ← иконки
])
).filter((u): u is string => Boolean(u));
}, [segments]);
// предзагрузка всех логотипов
usePreloadImages(preloadUrls);
// ---------------------------
@ -328,11 +356,42 @@ export default function KeyBusinessSegments() {
const denom = Math.max(1, N - 1);
const defaultCenterIndex = useMemo(() => Math.floor(N / 2), [N]);
const [activeIndex, setActiveIndex] = useState<number>(defaultCenterIndex);
const [activeIndex, _setActiveIndex] = useState<number>(defaultCenterIndex);
const [hoverIndex, setHoverIndex] = useState<number | null>(null);
const [isOverflow, setIsOverflow] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const lockTimer = useRef<number | null>(null);
// обёртка для смены индекса
const setActiveIndex = (idx: number) => {
if (idx === activeIndex) return;
_setActiveIndex(idx);
setIsLocked(true);
if (lockTimer.current) clearTimeout(lockTimer.current);
lockTimer.current = window.setTimeout(() => {
setIsLocked(false);
}, 250); // чуть больше transition
};
useEffect(() => {
const el = scrollerRef.current;
if (!el) return;
let raf = 0;
const onScroll = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
if (isLocked) return; // 🔒 во время анимации не пересчитываем
setActiveIndex(indexFromScroll(el.scrollLeft));
});
};
el.addEventListener("scroll", onScroll, { passive: true });
return () => el.removeEventListener("scroll", onScroll);
}, [denom, isLocked]);
/* ===== reserve-height measuring ===== */
const gridWrapRef = useRef<HTMLDivElement>(null);
const [gridMinHeight, setGridMinHeight] = useState(0);
@ -393,20 +452,6 @@ export default function KeyBusinessSegments() {
return () => window.removeEventListener("resize", onResize);
}, [defaultCenterIndex, N]);
useEffect(() => {
const el = scrollerRef.current;
if (!el) return;
let raf = 0;
const onScroll = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
setActiveIndex(indexFromScroll(el.scrollLeft));
});
};
el.addEventListener("scroll", onScroll, { passive: true });
return () => el.removeEventListener("scroll", onScroll);
}, [denom]);
const realActiveIndex = hoverIndex ?? activeIndex;
const activeSegment = segments[Math.min(Math.max(realActiveIndex, 0), N - 1)];
@ -471,9 +516,16 @@ export default function KeyBusinessSegments() {
{block.items.map((item) => (
<BulletItem key={item.id} $span={item.span}>
<ItemRow>
<CheckDot
<img
src={item.infoLogo}
alt=""
style={{
backgroundImage: item.infoLogo ? `url(${item.infoLogo})` : undefined,
width: 32,
height: 32,
borderRadius: 4,
objectFit: "contain",
background: "#16a34a",
padding: 4,
}}
/>
<Text18_400_dom

View File

@ -405,7 +405,7 @@ const Row = styled.div<{ $gap: number; $hidden?: boolean }>`
display: flex;
gap: ${(p) => p.$gap}px;
visibility: ${(p) => (p.$hidden ? "hidden" : "visible")};
padding: 0px 10px;
margin-left:10px;
`;
const Frame = styled.div`