Der sehr geehrte Herr Christian: Mobiles Chrome Browser pointer event Desaster

Sehr geehrte Damen und Herren! Sehr geehrtes Dazwischen! Liebe Kinder,

mich treibt der mobile Chrome Browser gerade in den Wahnsinn (Firefox ist brav).

"pointercancel" scheint post "pointerdown" gefolgt von "pointermove" ausgelöst zu werden (daher, kommt das Element nach "pointermove" "zum Stillstand", wird plötzlich handlePointerCancelQuirk ausgeführt).

Dieses Phänomen tritt nur auf dem tatsächlich mobilen Endgerät (Handy) auf, die Mobilanzeige der Desktop Chrome-Entwicklertools zeigt dieses Verhalten NICHT (daher, wer dieses Verhalten testen will, bitte lokal einen Webserver starten und mit dem mobilen Endgerät auf Chrome aufrufen).

Diesbezügliche Präzedenz suche ich im WWW vergeblich - ist das ein bekannter Chrome-Quirk? Irgendwelche Workarounds?

Danke für Rat und Tat nach anfänglich intensivem Augenrollen.

LG, Christian.

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mobile Chrome Quirks</title>
    <style>
      * {
        -webkit-user-select: none;
        user-select: none; /* TEMPORÄR UM user-select AUSZUSCHLIESSEN, bitte keine Kritik, danke Gunnar */
      }
      .dragging {
        z-index: 10;
        position: absolute;
        pointer-events: none;
        background-color: orangered;
        touch-action: none; /* Key fix for Chrome mobile? */
      }
      /*

      <
      ~~~~~~~~~~~~~~~~~~~~~~~~
      VERSUCH 1:
      Hilft fixed width/height ?
      >

      */
      article * {
        padding: 1rem;
        background-color: mediumaquamarine;
        width: 100px;
        height: 100px;
      }
      /*

      </
      ~~~~~~~~~~~~~~~~~~~~~~~~
      VERSUCH 1:
      Hilft fixed width/height ?
      > ...NOPE

      */
    </style>
  </head>
  <body>
    <main>
      <section>
        <h2>My Section!</h2>
        <article>
          <h3 class="name">My Important Article!</h3>
          <p class="other">A, G, X</p>
          <p class="beschreibung">Item 1, Item 2, Item 3, Item 4</p>
          <p class="preis">zu teuer</p>
        </article>
      </section>
    </main>
    <script>
      const main = document.querySelector("main");
      let shiftX, shiftY;
      let dragged = null;
      // Critical: touchmove scrolling verhindern
      function preventScrollWhileDragging(e) {
        e.preventDefault();
      }

      window.addEventListener("touchmove", preventScrollWhileDragging, {
        passive: false,
      });

      function moveAt(pageX, pageY) {
        dragged.style.left = pageX - shiftX + "px";
        dragged.style.top = pageY - shiftY + "px";
      }

      window.addEventListener("pointerdown", (e) => {
        e.preventDefault();
        let el = e.target;
        /*

        <
        ~~~~~~~~~~~~~~~~~~~~~~~~
        VERSUCH 2:
        Hilft pointerCapture ?
        >

        */
        el.setPointerCapture(e.pointerId);
        /*

        </
        ~~~~~~~~~~~~~~~~~~~~~~~~
        VERSUCH 2:
        Hilft pointerCapture ?
        > ...NOPE

        */
        const rect = el.getBoundingClientRect();
        shiftX = e.clientX - rect.left;
        shiftY = e.clientY - rect.top;
        dragged = e.target;

        e.target.classList.add("dragging");
        window.addEventListener("pointermove", onPointerMove, {
          passive: false,
        });
        window.addEventListener("pointerup", onPointerUp, { passive: false });
        window.addEventListener("pointercancel", onPointerUp, {
          passive: false,
        });
        moveAt(e.pageX, e.pageY);
      });

      function onPointerMove(e) {
        e.preventDefault();
        moveAt(e.pageX, e.pageY);
      }

      function onPointerUp(e) {
        e.preventDefault();
        e.target.releasePointerCapture(e.pointerId);
        e.target.classList.remove("dragging");
        window.removeEventListener("pointermove", onPointerMove, {
          passive: false,
        });
        window.removeEventListener("pointerup", onPointerUp, {
          passive: false,
        });
        window.removeEventListener("pointercancel", onPointerUp, {
          passive: false,
        });
      }

      // QUIRK

      function handlePointerCancelQuirk(e) {
        const myLog = `Pointer cancel on: ${e.target.innerHTML}`;
        document.body.innerHTML = myLog;
        document.body.innerHTML += `<button type="button" onclick="window.location.reload()">Refresh Page</button>`;
        console.log("Pointer cancel on:", e.target, "at", e.clientX, e.clientY);
      }

      window.addEventListener("pointercancel", handlePointerCancelQuirk);
    </script>
  </body>
</html>

  1. Hallo Christian,

    welchen Zweck erfüllt pointer-events: none; bei gesetzter dragging-Klasse? Wenn Du für das dragging-Element die Pointer-Events verhinderst, könnte das ggf. Chrome auf die Idee bringen, dass kein Dragging mehr möglich ist. Ansonsten müsste ich auch erstmal recherchieren.

    Abgesehen davon finde ich es gruselig, Events ständig zu registrieren und zu deregistrieren. Warum nicht einfach registriert lassen, bei pointerup die dragged-Variable auf null setzen und in allen Events, die mit drag zu tun haben, erstmal prüfen, ob dragged gesetzt ist. Wenn nicht, einfach nichts tun.

    Für's live logging kannst Du auch console.log verwenden und Dich vom Desktop zum Remote-Debugging mit dem Chrome auf dem Handy verbinden (hat ein paar Voraussetzungen, z.B. muss ein Android-Handy im Entwicklermodus sein).

    Oder Du machst ein div mit id="logContainer" in den Footer und hängst da die Logeinträge ein. Bleistiftsweise so (die fancy-Version hat ein ul als logContainer und fügt li Elemente ein)

    function log(text) {
       const item = document.createElement("div");
       item.textContent = text;
       document.querySelector("#logContainer").prepend(item);
    }
    

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      kannst du mal bitte testen, ob das hier unter Android funktioniert?

      https://wiki.selfhtml.org/wiki/JavaScript/Tutorials/Drag_and_Drop#Das_vollst.C3.A4ndige_Drag_and_Drop_Script

      @Christian: Willst du so etwas bauen?

      Gruß
      Jürgen

      1. Hallo JürgenB,

        funktioniert prima (Chrome auf Android mit Samsung-Aroma)

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          danke. Dann muss Christian irgendwas falsch gemacht haben. Aber mangels Online-Beispiel konnte ich das auf dem Handy meines Besuchs nicht ansehen.

          Gruß
          Jürgen

          1. Hallo JürgenB,

            hier ist mein Upload dazu (etwas modifiziert, vor allem die Eventregistrierung)

            https://www.borchmann-one.de/selfhtml/draggi.html

            Da passiert der Fehler auch. Ich verstehe es nicht.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. Hallo Rolf,

              hier ist mein Upload dazu (etwas modifiziert, vor allem die Eventregistrierung)

              danke. Leider habe ich jetzt erst mal keinen Zugriff mehr auf ein (Android-)Smartphone mit Chrome.

              Gruß
              Jürgen

              1. Wenn ich mich als OP zu später Stunde auch nochmals kurz und knackig einbringen darf:

                Eine weitere Vermutung meinerseits war, dass ein unbeabsichtigter partiell eingeleiteter Window-Reload beobachtetes Fehlverhalten zur Folge haben könnte (da ja Standardverhalten in mobilen Browsern bei vertikalem Overscroll "unter 0"!). Allerdings kann auch

                html,
                body {
                  overscroll-behavior: none;
                }
                

                dem lieben mobilitätsaffinen Google Chrome-Kollegen seine Flausen nicht austreiben.

                Ich bin mir mittlerweile ZIEMLICH SICHER dass ich da tatsächlich auf einen handfesten Google Chrome BUG gestoßen bin!

                Selbst wenn ich die Drag & Drop Funktionalität zurückfahre auf:

                </style>
                    html,
                    body {
                      overscroll-behavior: none;
                    }
                </style>
                </head>
                <body>
                  <script>
                    window.addEventListener("pointerdown", (e) => {
                      e.preventDefault();
                    });
                    window.addEventListener("pointermove", (e) => {
                      e.preventDefault();
                    });
                    window.addEventListener("pointercancel", handlePointerCancelQuirk);
                    // ...
                  </script>
                  // etc.
                

                zeigt der mobilen Chrome Browser diesselbe Kreativität!

                ==> Bin dafür, diesen Chrome Quirk zu eskalieren; eventuelle Behandlung im WIKI?

                LG, Christian

                1. Hallo Christian,

                  es ist definitiv ein Sonderfall. Unsere eigene Draggable-Lösung hat keine Probleme (ich habe eine Überwachung von pointercancel eingebaut und nach https://www.borchmann-one.de/selfhtml/selfdrag.html hochgeladen) dieser Art.

                  ABER: Wenn ich außerhalb des draggable Elements klicke und zu ziehen beginne, DANN schlägt dort die pointercancel Überwachung zu. Es ist mir jetzt zu spät, das genauer zu evaluieren und morgen werde ich dafür keine Zeit haben. Aber hier scheint eine Möglichkeit zu bestehen.

                  Wie sieht es bei iChrome aus? Mittlerweile darf der doch auch mit eigener Rendering-Engine laufen, wenn ich mich nicht irre. Kann das jemand testen?

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Hallo

                    Wie sieht es bei iChrome aus?

                    Verchromte Eier? 😆

                    Mittlerweile darf der doch auch mit eigener Rendering-Engine laufen, wenn ich mich nicht irre. Kann das jemand testen?

                    Ja, darf er. Seit iOS 17.4, in der EU. Ob das schon so ist, steht auf einem anderen Blatt. Der Firefox hat noch nicht seine eigene Rendering-Engine, aber die haben auch nicht die Ressourcen von Google.

                    Tschö, Auge

                    --
                    „Habe ich mir das nur eingebildet, oder kann der kleine Hund wirklich sprechen?“ fragte Schnapper. „Er behauptet, nicht dazu imstande zu sein“ erwiderte Victor. Schnapper zögerte (…) „Nun …“ sagte er schließlich, „ich schätze, er muss es am besten wissen.“ Terry Prattchett, Voll im Bilde
                  2. Hallo Christian,

                    es ist definitiv ein Sonderfall. Unsere eigene Draggable-Lösung hat keine Probleme (ich habe eine Überwachung von pointercancel eingebaut und nach https://www.borchmann-one.de/selfhtml/selfdrag.html hochgeladen) dieser Art.

                    Der Aufwand, mit dem hier das Rad neu erfunden und am Problem vorbei overengineered wird, ist geradezu abenteuerlich! 😂

                    @Rolf B Ich hoffe, du bist nicht allzu lange an draggi.html gesessen, der Funktionsumfangsauswuchs ist ja der reine Wahnsinn - es ist keine Schande, wenn man mal bzgl. Ursachenforschung einmal passen muss!

                    Credit where credit's due - ich muss allerdings wirklich sagen, dass der Einsatz hier im Forum seinesgleichen sucht, alle Achtung! 👍


                    Des Pudels Kern war übrigens letztlich tatsächlich trivial: ein simples touch-action: none; genügte - allerdings auf höherer Ebene, nicht dem Element selbst. Ob/warum Google Chrome Mobile dieses Verhalten TATSÄCHLICH GENAUSO intendiert steht allerdings auf einem anderen Blatt...

                    LG, Der sehr geehrter Herr Christian

                    1. Hallo Christian,

                      Rolf B Ich hoffe, du bist nicht allzu lange an draggi.html gesessen

                      Das war der Code den du gepostet hast, leicht verändert.

                      Rolf

                      --
                      sumpsi - posui - obstruxi
    2. @JürgenB

      Nein. Ich bin auf der Ursachenforschung meines vorgebrachten Problems.

      @Rolf B

      welchen Zweck erfüllt pointer-events: none; bei gesetzter dragging-Klasse? Wenn Du für das dragging-Element die Pointer-Events verhinderst, könnte das ggf. Chrome auf die Idee bringen, dass kein Dragging mehr möglich ist. Ansonsten müsste ich auch erstmal recherchieren.

      Fair point. Ich habe mutwillig einfach alles Mögliche deaktiviert, um sämtliche potentielle Fehlerquellen auszuschließen. Ist aber nicht ausschlaggebend, Mobile Chrome gebärdet sich auch ohne pointer-events: none; oder touch-action: none; hartnäckig.