Fotos mit dem Finger wischen (Swipe) — auf jeder Karte, überall auf der Seite
Das Foto-Karussell auf den Produktkarten (Shop-Grid, Startseite, Sale, Charakter-Kacheln, "Weitere Modelle" — überall wo ProductCard.astro verwendet wird) lässt sich jetzt zusätzlich zu den ‹/›-Pfeilen auch per Wisch-Geste bedienen: Finger auf dem Handy oder Maus ziehen am PC. Technisch über die Pointer-Events-API (deckt Touch UND Maus mit derselben Logik ab). Ein Wisch zählt erst ab einer Mindeststrecke und nur bei überwiegend horizontaler Bewegung, damit normales Scrollen der Seite nicht versehentlich als Wisch missverstanden wird. Da die ganze Karte ein Link ist, wird der nachfolgende Klick nach einem echten Wisch unterdrückt — sonst würde man ungewollt zur Produktseite springen, nur weil man durch die Fotos blättern wollte. Gleiches Wischen jetzt auch auf der Produktdetailseite selbst, für das große Hauptbild (springt zum nächsten/vorherigen Thumbnail). Verifiziert: Wisch ändert das Bild UND verhindert nachweislich die Navigation; normale Klicks navigieren weiterhin korrekt; die Pfeil- Buttons funktionieren unverändert (kein Regressionsfehler). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c7c500e23b
commit
ba3ab44ef3
@@ -96,11 +96,52 @@ const desc = product.beschreibung[lang];
|
|||||||
karussellWechseln(media, current + Number(control.dataset.dir));
|
karussellWechseln(media, current + Number(control.dataset.dir));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wischen/Swipen (Finger auf dem Handy, Maus ziehen am PC) — überall auf der Seite, wo eine
|
||||||
|
// Produktkarte mit mehreren Fotos vorkommt. Pointer Events statt separater Touch-/Maus-Logik,
|
||||||
|
// damit EIN Code sowohl Finger als auch Maus abdeckt. Ein "Swipe" zählt erst ab einer gewissen
|
||||||
|
// Mindeststrecke (SWIPE_SCHWELLE) und nur, wenn die Bewegung überwiegend horizontal war — sonst
|
||||||
|
// würde normales Scrollen der Seite (vertikal) versehentlich als Wisch-Geste missverstanden.
|
||||||
|
const SWIPE_SCHWELLE = 40;
|
||||||
|
let wischStart: { media: Element; x: number; y: number } | null = null;
|
||||||
|
let letzterWischZeitpunkt = 0;
|
||||||
|
|
||||||
|
document.addEventListener("pointerdown", (e) => {
|
||||||
|
const media = (e.target as HTMLElement).closest("[data-carousel]");
|
||||||
|
if (!media) return;
|
||||||
|
if (media.querySelectorAll(".carousel-img").length <= 1) return;
|
||||||
|
wischStart = { media, x: e.clientX, y: e.clientY };
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("pointerup", (e) => {
|
||||||
|
if (!wischStart) return;
|
||||||
|
const { media, x, y } = wischStart;
|
||||||
|
wischStart = null;
|
||||||
|
const dx = e.clientX - x;
|
||||||
|
const dy = e.clientY - y;
|
||||||
|
if (Math.abs(dx) < SWIPE_SCHWELLE || Math.abs(dx) < Math.abs(dy)) return;
|
||||||
|
letzterWischZeitpunkt = Date.now();
|
||||||
|
const imgs = [...media.querySelectorAll<HTMLElement>(".carousel-img")];
|
||||||
|
const current = Math.max(0, imgs.findIndex((img) => img.style.display !== "none"));
|
||||||
|
karussellWechseln(media, current + (dx < 0 ? 1 : -1));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("pointercancel", () => { wischStart = null; });
|
||||||
|
|
||||||
|
// Direkt nach einem echten Wisch darf der Klick NICHT mehr zur Produktseite weiterspringen
|
||||||
|
// (sonst landet man ungewollt auf der Produktseite, nur weil man das Foto durchblättern
|
||||||
|
// wollte). Capture-Phase, damit das VOR dem normalen Link-Klick des <a class="product-card">
|
||||||
|
// greift. Ein kurzes Zeitfenster (300ms) reicht, da Klick direkt nach dem Loslassen kommt.
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (Date.now() - letzterWischZeitpunkt > 300) return;
|
||||||
|
const link = (e.target as HTMLElement).closest("a.product-card");
|
||||||
|
if (link) { e.preventDefault(); e.stopPropagation(); }
|
||||||
|
}, true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.product-card { display: flex; flex-direction: column; gap: 0.6rem; padding: 1rem; }
|
.product-card { display: flex; flex-direction: column; gap: 0.6rem; padding: 1rem; }
|
||||||
.product-media { position: relative; isolation: isolate; }
|
.product-media { position: relative; isolation: isolate; touch-action: pan-y; }
|
||||||
.product-media .img-placeholder { aspect-ratio: 1 / 1; min-height: 0; }
|
.product-media .img-placeholder { aspect-ratio: 1 / 1; min-height: 0; }
|
||||||
.product-badges { position: absolute; top: 0.6rem; left: 0.6rem; display: flex; gap: 0.4rem; flex-wrap: wrap; z-index: 2; }
|
.product-badges { position: absolute; top: 0.6rem; left: 0.6rem; display: flex; gap: 0.4rem; flex-wrap: wrap; z-index: 2; }
|
||||||
|
|
||||||
|
|||||||
@@ -209,9 +209,26 @@ const felder: [string, string | undefined][] = [
|
|||||||
|
|
||||||
// Klick auf ein Miniaturbild zeigt es groß im Hauptbild an (mehrere Produktfotos).
|
// Klick auf ein Miniaturbild zeigt es groß im Hauptbild an (mehrere Produktfotos).
|
||||||
const heroImg = document.getElementById("product-hero-img") as HTMLImageElement | null;
|
const heroImg = document.getElementById("product-hero-img") as HTMLImageElement | null;
|
||||||
document.querySelectorAll<HTMLImageElement>(".thumb.product-photo").forEach((thumb) => {
|
const thumbs = [...document.querySelectorAll<HTMLImageElement>(".thumb.product-photo")];
|
||||||
|
thumbs.forEach((thumb) => {
|
||||||
thumb.addEventListener("click", () => {
|
thumb.addEventListener("click", () => {
|
||||||
if (heroImg && thumb.dataset.full) heroImg.src = thumb.dataset.full;
|
if (heroImg && thumb.dataset.full) heroImg.src = thumb.dataset.full;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Auch das große Hauptbild lässt sich wischen (Finger auf dem Handy, Maus ziehen am PC) — springt
|
||||||
|
// dabei einfach zum nächsten/vorherigen Miniaturbild, exakt wie ein Klick auf den Thumbnail.
|
||||||
|
if (heroImg && thumbs.length > 1) {
|
||||||
|
let startX = 0, startY = 0;
|
||||||
|
heroImg.style.touchAction = "pan-y";
|
||||||
|
heroImg.addEventListener("pointerdown", (e) => { startX = e.clientX; startY = e.clientY; });
|
||||||
|
heroImg.addEventListener("pointerup", (e) => {
|
||||||
|
const dx = e.clientX - startX;
|
||||||
|
const dy = e.clientY - startY;
|
||||||
|
if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy)) return;
|
||||||
|
const aktuellerIndex = Math.max(0, thumbs.findIndex((th) => th.dataset.full && heroImg.src.endsWith(th.dataset.full)));
|
||||||
|
const naechster = ((aktuellerIndex + (dx < 0 ? 1 : -1)) % thumbs.length + thumbs.length) % thumbs.length;
|
||||||
|
heroImg.src = thumbs[naechster].dataset.full!;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user