Noms de variables et fonctions explicites (sans abréviation), fonctions unitaires, paths SVG construits via template literals avec constantes nommées. Découpage en assets/js/characters/jump.js et pupilTracking.js, importés dans script.js. Clamp de la rotation des pieds pour éviter le bounce à l'atterrissage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.4 KiB
JavaScript
58 lines
2.4 KiB
JavaScript
export function initPupilTracking(svg) {
|
|
const VIEWBOX_WIDTH = 304;
|
|
const VIEWBOX_HEIGHT = 259;
|
|
const PUPIL_SCALE = 1.267471;
|
|
const PUPIL_BASE_TRANSLATE_X = PUPIL_SCALE * 52.5;
|
|
const PUPIL_BASE_TRANSLATE_Y = PUPIL_SCALE * 87.5;
|
|
const PUPIL_MAX_OFFSET_RADIUS = 3.5;
|
|
const PUPIL_LERP_FACTOR = 0.12;
|
|
|
|
// Centre de chaque œil — personnage gauche uniquement
|
|
const eyes = [
|
|
{ id: "left-pupil", centerX: 54.03, centerY: 86.79 },
|
|
{ id: "right-pupil", centerX: 103.55, centerY: 86.79 },
|
|
];
|
|
|
|
const currentPupilPositions = eyes.map((eye) => ({ x: eye.centerX, y: eye.centerY }));
|
|
const targetPupilPositions = eyes.map((eye) => ({ x: eye.centerX, y: eye.centerY }));
|
|
|
|
document.addEventListener("mousemove", (event) => {
|
|
const svgBoundingRect = svg.getBoundingClientRect();
|
|
const mouseViewportX = (event.clientX - svgBoundingRect.left) * (VIEWBOX_WIDTH / svgBoundingRect.width);
|
|
const mouseViewportY = (event.clientY - svgBoundingRect.top) * (VIEWBOX_HEIGHT / svgBoundingRect.height);
|
|
|
|
eyes.forEach((eye, eyeIndex) => {
|
|
const deltaX = mouseViewportX - eye.centerX;
|
|
const deltaY = mouseViewportY - eye.centerY;
|
|
const distanceToMouse = Math.hypot(deltaX, deltaY);
|
|
const clampRatio = distanceToMouse > 0 ? Math.min(PUPIL_MAX_OFFSET_RADIUS, distanceToMouse) / distanceToMouse : 0;
|
|
targetPupilPositions[eyeIndex].x = eye.centerX + deltaX * clampRatio;
|
|
targetPupilPositions[eyeIndex].y = eye.centerY + deltaY * clampRatio;
|
|
});
|
|
});
|
|
|
|
document.addEventListener("mouseleave", () => {
|
|
eyes.forEach((eye, eyeIndex) => {
|
|
targetPupilPositions[eyeIndex].x = eye.centerX;
|
|
targetPupilPositions[eyeIndex].y = eye.centerY;
|
|
});
|
|
});
|
|
|
|
function animatePupils() {
|
|
eyes.forEach((eye, eyeIndex) => {
|
|
currentPupilPositions[eyeIndex].x +=
|
|
(targetPupilPositions[eyeIndex].x - currentPupilPositions[eyeIndex].x) * PUPIL_LERP_FACTOR;
|
|
currentPupilPositions[eyeIndex].y +=
|
|
(targetPupilPositions[eyeIndex].y - currentPupilPositions[eyeIndex].y) * PUPIL_LERP_FACTOR;
|
|
document
|
|
.getElementById(eye.id)
|
|
?.setAttribute(
|
|
"transform",
|
|
`matrix(${PUPIL_SCALE},0,0,${PUPIL_SCALE},${currentPupilPositions[eyeIndex].x - PUPIL_BASE_TRANSLATE_X},${currentPupilPositions[eyeIndex].y - PUPIL_BASE_TRANSLATE_Y})`
|
|
);
|
|
});
|
|
requestAnimationFrame(animatePupils);
|
|
}
|
|
|
|
requestAnimationFrame(animatePupils);
|
|
}
|