Mobile: lighter render path + touch-to-accelerate the plane

Detects mobile via UA + 768 px media query (IS_MOBILE constant) and
flips three knobs:

- Renderer: antialias off, pixelRatio capped at 1.5 (vs 2 on desktop)
- Parcels: spawn every 2.4 s (vs 1.4 s on desktop) to keep clone+
  draw cost down

Adds touch handlers so users without a mouse can speed the plane up:

- touchstart bumps `touchBoost` from 1 to 6 → BASE_SPEED is multiplied
  by 6 each tick while a finger is on the screen
- touchmove also feeds Math.hypot(dx, dy) * MOUSE_BOOST into
  targetProgress, so swiping advances the plane the same way mouse
  motion does on desktop
- touchend / touchcancel reset touchBoost to 1 and clear last-touch
  coordinates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MVA Global Fret 2026-05-05 14:41:34 +02:00
parent 0b2fe83963
commit fd0acea058

View File

@ -12,12 +12,20 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
/* Détection mobile
Sur mobile : on coupe l'antialiasing, on plafonne le pixel ratio à
1.5 et on espace davantage les spawns de colis pour garder ~60 fps. */
const IS_MOBILE = /iPhone|iPad|iPod|Android|Mobile/i.test(navigator.userAgent)
|| window.matchMedia('(max-width: 768px)').matches;
/* ── Renderer & camera ─────────────────────────────────────────────────── */ /* ── Renderer & camera ─────────────────────────────────────────────────── */
const canvas = document.getElementById('three-canvas'); const canvas = document.getElementById('three-canvas');
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
canvas, alpha: true, antialias: true, powerPreference: 'high-performance' canvas, alpha: true,
antialias: !IS_MOBILE,
powerPreference: 'high-performance'
}); });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setPixelRatio(Math.min(window.devicePixelRatio, IS_MOBILE ? 1.5 : 2));
renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.05; renderer.toneMappingExposure = 1.05;
@ -72,7 +80,7 @@ loader.load(
parachute (mêmes parent même position/rotation/échelle), et placée parachute (mêmes parent même position/rotation/échelle), et placée
pile au niveau du harnais pour que l'attachement soit lisible. pile au niveau du harnais pour que l'attachement soit lisible.
*/ */
const PARCEL_SPAWN_EVERY = 1.4; const PARCEL_SPAWN_EVERY = IS_MOBILE ? 2.4 : 1.4; // moins fréquent sur mobile
const PARCEL_FALL_SPEED = 1.4; const PARCEL_FALL_SPEED = 1.4;
const PARCEL_DRIFT = 0.45; const PARCEL_DRIFT = 0.45;
const PARCEL_INITIAL_SCALE = 0.45; const PARCEL_INITIAL_SCALE = 0.45;
@ -237,6 +245,38 @@ window.addEventListener('deviceorientation', (e) => {
mouse.py = Math.max(0, Math.min(1, (e.beta - 20) / 60)); mouse.py = Math.max(0, Math.min(1, (e.beta - 20) / 60));
}, { passive: true }); }, { passive: true });
/* Tactile : tant que le doigt est posé, l'avion accélère (multiplicateur
sur BASE_SPEED). Le swipe ajoute aussi du progrès cumulé comme la
souris sur desktop. */
let touchBoost = 1;
let lastTouchX = null, lastTouchY = null;
window.addEventListener('touchstart', (e) => {
touchBoost = 6;
if (e.touches[0]) {
lastTouchX = e.touches[0].clientX;
lastTouchY = e.touches[0].clientY;
}
}, { passive: true });
window.addEventListener('touchmove', (e) => {
const t0 = e.touches[0];
if (!t0) return;
if (lastTouchX !== null) {
const dx = t0.clientX - lastTouchX;
const dy = t0.clientY - lastTouchY;
mouse.targetProgress = Math.min(1, mouse.targetProgress + Math.hypot(dx, dy) * MOUSE_BOOST);
}
lastTouchX = t0.clientX;
lastTouchY = t0.clientY;
mouse.px = t0.clientX / window.innerWidth;
mouse.py = t0.clientY / window.innerHeight;
}, { passive: true });
function endTouch() { touchBoost = 1; lastTouchX = lastTouchY = null; }
window.addEventListener('touchend', endTouch, { passive: true });
window.addEventListener('touchcancel', endTouch, { passive: true });
const root = document.documentElement; const root = document.documentElement;
/* Révélation du CTA /* Révélation du CTA
@ -255,8 +295,9 @@ function tick() {
const dt = Math.min(0.1, t - lastT); // clamp pour éviter les bonds après onglet en arrière-plan const dt = Math.min(0.1, t - lastT); // clamp pour éviter les bonds après onglet en arrière-plan
lastT = t; lastT = t;
/* Avance autonome + cible cumulée par la souris, le tout limité à 1 */ /* Avance autonome + boost tactile (×6 tant qu'un doigt touche l'écran)
mouse.targetProgress = Math.min(1, mouse.targetProgress + BASE_SPEED * dt); + cible cumulée par souris/swipe, le tout limité à 1 */
mouse.targetProgress = Math.min(1, mouse.targetProgress + BASE_SPEED * touchBoost * dt);
/* Lerp doux pour fluidifier */ /* Lerp doux pour fluidifier */
mouse.progress += (mouse.targetProgress - mouse.progress) * 0.06; mouse.progress += (mouse.targetProgress - mouse.progress) * 0.06;
const p = mouse.progress; const p = mouse.progress;