Files

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);
}