From 9f503c011d2f8f64945dc7517d1334def717a370 Mon Sep 17 00:00:00 2001 From: Mischa Date: Sun, 21 Jun 2026 21:36:48 +0200 Subject: [PATCH] fix(photoswipe): keyboard arrow animation via CSS keyframes Previous approach (CSS transition + reflow trick) is unreliable in Firefox. New approach: PhotoSwipe emits 'change' synchronously before painting; we add a direction-aware CSS keyframe animation to the incoming slide element, with animation-fill-mode:both so there is no flash before the animation starts. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr --- themes/intotheeast/css/style.css | 13 +++++++++ .../intotheeast/templates/dailies.html.twig | 28 +++++++++++++++---- themes/intotheeast/templates/home.html.twig | 28 +++++++++++++++---- themes/intotheeast/templates/trip.html.twig | 28 +++++++++++++++---- 4 files changed, 79 insertions(+), 18 deletions(-) diff --git a/themes/intotheeast/css/style.css b/themes/intotheeast/css/style.css index 9b5c6e0..d2e30f8 100644 --- a/themes/intotheeast/css/style.css +++ b/themes/intotheeast/css/style.css @@ -459,6 +459,19 @@ body::after { /* iOS Safari: 100vh freezes when address bar hides; dvh tracks the live viewport */ .pswp { height: 100dvh; } +/* Keyboard arrow navigation slide-in animations */ +.pswp-key-from-right { animation: pswpKeyFromRight 0.35s cubic-bezier(0.4, 0, 0.22, 1) both; } +.pswp-key-from-left { animation: pswpKeyFromLeft 0.35s cubic-bezier(0.4, 0, 0.22, 1) both; } + +@keyframes pswpKeyFromRight { + from { transform: translateX(48px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} +@keyframes pswpKeyFromLeft { + from { transform: translateX(-48px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + /* ── Map page ────────────────────────────────────────────────────────────────── */ .map-page .site-main { max-width: none; padding: 0; } diff --git a/themes/intotheeast/templates/dailies.html.twig b/themes/intotheeast/templates/dailies.html.twig index a963138..e151acd 100644 --- a/themes/intotheeast/templates/dailies.html.twig +++ b/themes/intotheeast/templates/dailies.html.twig @@ -111,15 +111,31 @@ const lightbox = new PhotoSwipeLightbox({ pswpModule: () => import('https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe.esm.min.js') }); lightbox.on('afterOpen', function () { - var container = lightbox.pswp.element.querySelector('.pswp__container'); + var pswp = lightbox.pswp; + var keyDir = 0; + var clearTimer = null; function onKey(e) { - if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; - container.style.transition = 'transform 0.35s cubic-bezier(0.4, 0, 0.22, 1)'; - container.getBoundingClientRect(); - setTimeout(function () { container.style.transition = ''; }, 400); + if (e.key === 'ArrowRight') keyDir = 1; + else if (e.key === 'ArrowLeft') keyDir = -1; + else keyDir = 0; } document.addEventListener('keydown', onKey, true); - lightbox.pswp.on('close', function () { document.removeEventListener('keydown', onKey, true); }); + pswp.on('change', function () { + if (!keyDir) return; + var dir = keyDir; + keyDir = 0; + var el = pswp.currSlide && pswp.currSlide.el; + if (!el) return; + el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); + el.offsetWidth; + el.classList.add(dir > 0 ? 'pswp-key-from-right' : 'pswp-key-from-left'); + clearTimeout(clearTimer); + clearTimer = setTimeout(function () { el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); }, 400); + }); + pswp.on('close', function () { + document.removeEventListener('keydown', onKey, true); + clearTimeout(clearTimer); + }); }); lightbox.init(); diff --git a/themes/intotheeast/templates/home.html.twig b/themes/intotheeast/templates/home.html.twig index 33ca3fd..a72380a 100644 --- a/themes/intotheeast/templates/home.html.twig +++ b/themes/intotheeast/templates/home.html.twig @@ -146,15 +146,31 @@ const lightbox = new PhotoSwipeLightbox({ pswpModule: () => import('https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe.esm.min.js') }); lightbox.on('afterOpen', function () { - var container = lightbox.pswp.element.querySelector('.pswp__container'); + var pswp = lightbox.pswp; + var keyDir = 0; + var clearTimer = null; function onKey(e) { - if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; - container.style.transition = 'transform 0.35s cubic-bezier(0.4, 0, 0.22, 1)'; - container.getBoundingClientRect(); - setTimeout(function () { container.style.transition = ''; }, 400); + if (e.key === 'ArrowRight') keyDir = 1; + else if (e.key === 'ArrowLeft') keyDir = -1; + else keyDir = 0; } document.addEventListener('keydown', onKey, true); - lightbox.pswp.on('close', function () { document.removeEventListener('keydown', onKey, true); }); + pswp.on('change', function () { + if (!keyDir) return; + var dir = keyDir; + keyDir = 0; + var el = pswp.currSlide && pswp.currSlide.el; + if (!el) return; + el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); + el.offsetWidth; + el.classList.add(dir > 0 ? 'pswp-key-from-right' : 'pswp-key-from-left'); + clearTimeout(clearTimer); + clearTimer = setTimeout(function () { el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); }, 400); + }); + pswp.on('close', function () { + document.removeEventListener('keydown', onKey, true); + clearTimeout(clearTimer); + }); }); lightbox.init(); diff --git a/themes/intotheeast/templates/trip.html.twig b/themes/intotheeast/templates/trip.html.twig index 97a6ee7..51bcab9 100644 --- a/themes/intotheeast/templates/trip.html.twig +++ b/themes/intotheeast/templates/trip.html.twig @@ -515,15 +515,31 @@ const lightbox = new PhotoSwipeLightbox({ pswpModule: () => import('https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe.esm.min.js') }); lightbox.on('afterOpen', function () { - var container = lightbox.pswp.element.querySelector('.pswp__container'); + var pswp = lightbox.pswp; + var keyDir = 0; + var clearTimer = null; function onKey(e) { - if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; - container.style.transition = 'transform 0.35s cubic-bezier(0.4, 0, 0.22, 1)'; - container.getBoundingClientRect(); // force reflow so browser commits current position as "from" - setTimeout(function () { container.style.transition = ''; }, 400); + if (e.key === 'ArrowRight') keyDir = 1; + else if (e.key === 'ArrowLeft') keyDir = -1; + else keyDir = 0; } document.addEventListener('keydown', onKey, true); - lightbox.pswp.on('close', function () { document.removeEventListener('keydown', onKey, true); }); + pswp.on('change', function () { + if (!keyDir) return; + var dir = keyDir; + keyDir = 0; + var el = pswp.currSlide && pswp.currSlide.el; + if (!el) return; + el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); + el.offsetWidth; // restart animation if navigating rapidly + el.classList.add(dir > 0 ? 'pswp-key-from-right' : 'pswp-key-from-left'); + clearTimeout(clearTimer); + clearTimer = setTimeout(function () { el.classList.remove('pswp-key-from-left', 'pswp-key-from-right'); }, 400); + }); + pswp.on('close', function () { + document.removeEventListener('keydown', onKey, true); + clearTimeout(clearTimer); + }); }); lightbox.init();