65 lines
2.2 KiB
JavaScript
65 lines
2.2 KiB
JavaScript
// 3D-наклон портрета на главной по движению курсора.
|
|
// Эффект "парящей карточки": карточка поворачивается к курсору,
|
|
// слои дают параллакс, поверх ложится мягкий блик.
|
|
|
|
const MAX_TILT = 9; // градусов по каждой оси
|
|
const GLIDE = 320; // ms возврата в покой
|
|
|
|
function isTouchOrReduced() {
|
|
const touch = window.matchMedia("(hover: none), (pointer: coarse)").matches;
|
|
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
return touch || reduced;
|
|
}
|
|
|
|
export function initHero3DTilt() {
|
|
const shell = document.querySelector(".hero__portrait-shell");
|
|
if (!shell || isTouchOrReduced()) return;
|
|
|
|
let rafId = null;
|
|
let targetX = 0;
|
|
let targetY = 0;
|
|
let currentX = 0;
|
|
let currentY = 0;
|
|
let active = false;
|
|
|
|
function onMove(e) {
|
|
const rect = shell.getBoundingClientRect();
|
|
const px = (e.clientX - rect.left) / rect.width; // 0..1
|
|
const py = (e.clientY - rect.top) / rect.height; // 0..1
|
|
targetY = (px - 0.5) * 2 * MAX_TILT; // rotateY
|
|
targetX = (0.5 - py) * 2 * MAX_TILT; // rotateX
|
|
shell.style.setProperty("--tilt-px", `${px * 100}%`);
|
|
shell.style.setProperty("--tilt-py", `${py * 100}%`);
|
|
if (!active) {
|
|
active = true;
|
|
shell.classList.add("hero__portrait-shell--tilting");
|
|
}
|
|
if (!rafId) rafId = requestAnimationFrame(tick);
|
|
}
|
|
|
|
function tick() {
|
|
currentX += (targetX - currentX) * 0.18;
|
|
currentY += (targetY - currentY) * 0.18;
|
|
shell.style.setProperty("--tilt-rx", `${currentX.toFixed(2)}deg`);
|
|
shell.style.setProperty("--tilt-ry", `${currentY.toFixed(2)}deg`);
|
|
const done =
|
|
Math.abs(targetX - currentX) < 0.01 && Math.abs(targetY - currentX) < 0.01;
|
|
if (done && !active) {
|
|
rafId = null;
|
|
} else {
|
|
rafId = requestAnimationFrame(tick);
|
|
}
|
|
}
|
|
|
|
function onLeave() {
|
|
active = false;
|
|
targetX = 0;
|
|
targetY = 0;
|
|
shell.classList.remove("hero__portrait-shell--tilting");
|
|
if (!rafId) rafId = requestAnimationFrame(tick);
|
|
}
|
|
|
|
shell.addEventListener("mousemove", onMove);
|
|
shell.addEventListener("mouseleave", onLeave);
|
|
}
|