6aff8cd6d9
- HeroImage компонент: WebP с 3 размерами (800/1280/1920) + <picture> srcset - На десктопе: справа, fade с левого края, lёгкий parallax при скролле - На мобиле: сверху, фейд к контенту, без parallax - В тёмной теме картинка приглушается opacity 0.55 + filter - max-width текстового блока скорректирован чтобы не наезжать на иллюстрацию
122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
'use client';
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
/**
|
|
* Hero иллюстрация с лёгким parallax при скролле + плавный fade с левого края,
|
|
* чтобы картинка органично «вытаивала» в фон под текстом.
|
|
*/
|
|
export default function HeroImage() {
|
|
const ref = useRef(null);
|
|
|
|
useEffect(() => {
|
|
if (!ref.current) return;
|
|
let raf = 0;
|
|
const onScroll = () => {
|
|
cancelAnimationFrame(raf);
|
|
raf = requestAnimationFrame(() => {
|
|
const y = window.scrollY;
|
|
// едет вверх медленнее, чем страница
|
|
const offset = Math.min(60, y * 0.15);
|
|
if (ref.current) ref.current.style.transform = `translateY(-${offset}px)`;
|
|
});
|
|
};
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="hero-image-wrap" aria-hidden="true">
|
|
<picture>
|
|
<source
|
|
srcSet="/uploads/hero-1920.webp 1920w, /uploads/hero-1280.webp 1280w, /uploads/hero-800.webp 800w"
|
|
sizes="(min-width: 1024px) 65vw, 100vw"
|
|
type="image/webp"
|
|
/>
|
|
<img
|
|
ref={ref}
|
|
src="/uploads/hero-1280.webp"
|
|
alt=""
|
|
className="hero-image"
|
|
loading="eager"
|
|
fetchPriority="high"
|
|
decoding="async"
|
|
/>
|
|
</picture>
|
|
|
|
<style jsx>{`
|
|
.hero-image-wrap {
|
|
position: absolute;
|
|
top: 0; right: 0; bottom: 0;
|
|
width: 100%;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
z-index: -1;
|
|
}
|
|
.hero-image {
|
|
position: absolute;
|
|
top: -40px;
|
|
right: -2%;
|
|
width: auto;
|
|
height: 115%;
|
|
max-width: none;
|
|
object-fit: contain;
|
|
object-position: right top;
|
|
opacity: 0.85;
|
|
will-change: transform;
|
|
transition: opacity 0.4s ease;
|
|
/* плавный фейд от центра к левому краю — картинка тает */
|
|
-webkit-mask-image: linear-gradient(
|
|
to right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.4) 30%,
|
|
black 55%,
|
|
black 100%
|
|
);
|
|
mask-image: linear-gradient(
|
|
to right,
|
|
transparent 0%,
|
|
rgba(0,0,0,0.4) 30%,
|
|
black 55%,
|
|
black 100%
|
|
);
|
|
}
|
|
:global(.dark) .hero-image {
|
|
opacity: 0.55;
|
|
filter: brightness(0.85) saturate(0.9);
|
|
}
|
|
/* нижний fade-out к контенту */
|
|
.hero-image-wrap::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0; right: 0; bottom: 0;
|
|
height: 120px;
|
|
background: linear-gradient(to bottom, transparent, rgb(var(--bg)));
|
|
pointer-events: none;
|
|
}
|
|
/* На мобилке картинка сверху, под ней — текст */
|
|
@media (max-width: 767px) {
|
|
.hero-image-wrap {
|
|
position: relative;
|
|
width: 100vw;
|
|
margin-left: -1rem;
|
|
margin-right: -1rem;
|
|
height: 240px;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.hero-image {
|
|
top: 0;
|
|
right: 0;
|
|
height: 100%;
|
|
width: 100%;
|
|
object-position: right center;
|
|
opacity: 0.95;
|
|
-webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
|
|
mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
|
|
}
|
|
.hero-image-wrap::after { display: none; }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|