round67: touch-action von manipulation auf none korrigiert - Browser hat Zieh-Geste vorher fuer natives Scrollen beansprucht

This commit is contained in:
2026-07-26 20:38:00 +02:00
parent 8046120548
commit 3e27c54199

View File

@@ -33,14 +33,23 @@ import { useHomeLayout, type HomeSlot, type ItemRef } from "../hooks/useHomeLayo
* Zusätzlich: mobile Browser fangen ein Lang-Drücken auf einem <img> (bzw. * Zusätzlich: mobile Browser fangen ein Lang-Drücken auf einem <img> (bzw.
* dem umgebenden Element) standardmäßig SELBST ab und zeigen ihr eigenes * dem umgebenden Element) standardmäßig SELBST ab und zeigen ihr eigenes
* Kontextmenü ("Bild speichern"/"Bild kopieren" usw.), noch bevor unser * Kontextmenü ("Bild speichern"/"Bild kopieren" usw.), noch bevor unser
* eigener Drag-Timer greift (siehe Bugreport). Dagegen: draggable={false} + * eigener Drag-Timer greift. Dagegen: draggable={false} + pointer-events-none
* pointer-events-none auf den <img>-Tags (Touch-Events landen dadurch immer * auf den <img>-Tags (Touch-Events landen dadurch immer auf dem umgebenden
* auf dem umgebenden <button>), -webkit-touch-callout:none (unterdrückt das * <button>), -webkit-touch-callout:none (unterdrückt das iOS-Kontextmenü),
* iOS-Kontextmenü), onContextMenu-preventDefault (unterdrückt das * onContextMenu-preventDefault (unterdrückt das Android-Pendant).
* Android-Pendant) sowie touch-action:manipulation an den Kacheln kombiniert *
* mit einem gezielten preventDefault() in handlePointerMove NUR während * Außerdem (Korrektur nach erstem Anlauf): die Kacheln brauchen
* eines aktiven Drags (normales Scrollen bleibt dadurch möglich, wenn gerade * touch-action:"none" von Anfang an, NICHT "manipulation" - mit
* nicht gezogen wird). * "manipulation" beansprucht der Browser eine Zieh-nach-unten-Geste bereits
* für natives Scrollen, bevor unser preventDefault() in handlePointerMove
* überhaupt zum Zug kommt (führte dazu, dass beim Ziehen das
* Handy-Benachrichtigungsmenü von oben aufging statt das Icon zu bewegen).
* Mit touch-action:"none" überlässt der Browser Touch-Gesten auf den
* Kacheln von vornherein komplett uns. Nebeneffekt: ein Scroll-Versuch, der
* exakt auf einem Icon beginnt, scrollt nicht mehr nativ - das ist
* hinnehmbar (Wischen zwischen den Icons/auf dem Hintergrund scrollt
* weiterhin normal) und entspricht dem Verhalten eines echten
* Android-Homescreens, wo ein Icon selbst auch nicht als Scroll-Fläche dient.
* *
* Signatur-Element: der Wallpaper-Hintergrund besteht aus den tatsächlichen, * Signatur-Element: der Wallpaper-Hintergrund besteht aus den tatsächlichen,
* in den eigenen Kategorien hinterlegten Farben (Admin -> Kategorien). * in den eigenen Kategorien hinterlegten Farben (Admin -> Kategorien).
@@ -324,10 +333,13 @@ export function MobileAndroidHome({
return; return;
} }
if (e.pointerId !== drag.pointerId) return; if (e.pointerId !== drag.pointerId) return;
// Solange aktiv gezogen wird, das native Scrollen unterdrücken (siehe // Solange aktiv gezogen wird, das native Scrollen unterdrücken.
// touch-action: manipulation an den Kacheln - das erlaubt normales // WICHTIG (Korrektur): touch-action:"manipulation" reicht dafür NICHT -
// Scrollen, WENN nicht gezogen wird, verhindert aber, dass die Seite // das erlaubt dem Browser bereits VOR diesem preventDefault(), die Geste
// während des Ziehens selbst "wegscrollt"). // für natives Scrollen zu beanspruchen (führte dazu, dass beim Ziehen
// nach unten das Handy-Benachrichtigungsmenü aufging, siehe Bugreport).
// Die Kacheln nutzen daher touch-action:"none" von Anfang an - der
// Browser überlässt Touch-Gesten auf ihnen komplett uns/JavaScript.
e.preventDefault(); e.preventDefault();
setDrag((d) => (d ? { ...d, x: e.clientX, y: e.clientY } : d)); setDrag((d) => (d ? { ...d, x: e.clientX, y: e.clientY } : d));
@@ -428,7 +440,7 @@ export function MobileAndroidHome({
onPointerCancel={handlePointerCancel} onPointerCancel={handlePointerCancel}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
onClick={() => handleTileClick(() => setOpenFolderId(slot.id))} onClick={() => handleTileClick(() => setOpenFolderId(slot.id))}
style={{ touchAction: "manipulation", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }} style={{ touchAction: "none", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }}
className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`} className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`}
> >
<FolderTile icons={icons} dimmed={isDragging} /> <FolderTile icons={icons} dimmed={isDragging} />
@@ -453,7 +465,7 @@ export function MobileAndroidHome({
onPointerCancel={handlePointerCancel} onPointerCancel={handlePointerCancel}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
onClick={() => handleTileClick(() => onOpen({ url: item.url, id: item.id, kind: item.type }))} onClick={() => handleTileClick(() => onOpen({ url: item.url, id: item.id, kind: item.type }))}
style={{ touchAction: "manipulation", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }} style={{ touchAction: "none", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }}
className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`} className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`}
> >
<TileIcon favicon={item.favicon} label={item.displayName} dimmed={isDragging} /> <TileIcon favicon={item.favicon} label={item.displayName} dimmed={isDragging} />