こちらのissue に対応するために調査しました。
こんなことは普通はしないと思うのですが、iOSにてページスクロール中にscrollToを使うと、
❶ ユーザーがページのスクロールを開始する
❷ widow.scrollToでスクロール位置を変更する
❸ (指定箇所までページがスクロールする)
❹ ユーザーがtouchendを発生させないままページをスクロールする
❺ (widow.scrollTo発生前の本来の位置までスクロール位置が戻る) 👈
という挙動になるっぽいです。(iOS14.4にて確認)
つまり、touchendが発生するまでは、touchstartの位置が起点になるようです。
ちなみにAndroidのChromeの場合、
❶ ユーザーがページのスクロールを開始する
❷ widow.scrollToでスクロール位置を変更する
❸ (指定箇所までページがスクロールする)
❹ ユーザーがtouchendを発生させないままページをスクロールする
❺ (widow.scrollTo発生後の位置からスクロールする) 👈
という挙動になりました。
今回はAndroidの挙動をiOSでも再現するためにいろいろ探ってみました。
結論だけ先に書くと、windowのスクロールをAndroidと同じ挙動にするのは無理でした。
❌ touchendをdispatchする
setTimeout(() => { window.scrollTo(0, 100); document.body.dispatchEvent(new Event('touchend')); }, 1000);
こんなイメージです。
自作のtouchendを発火させてみたのですが、残念ながら何も起こりませんでした。
❌ widow.scrollToと同時にtouch-actionをnoneにする
setTimeout(() => { window.scrollTo(0, 100); document.body.style.touchAction = 'none'; setTimeout(() => { document.body.style.touchAction = 'auto'; }, 0); }, 1000);
こんなイメージです。
一瞬、touch-actionをnoneにしてスクロールを止め、autoに戻すという作戦です。
が。そもそも、autoに戻さなくても、touchendが発生するまではスクロールが止まりませんでした。
つまり、
❶ ユーザーがページのスクロールを開始する
❷ widow.scrollToでスクロール位置を変更し、touch-actionをnoneにする
❸ (指定箇所までページがスクロールする)
❹ ユーザーがtouchendを発生させないままページをスクロールする
❺ (widow.scrollTo発生後の位置からスクロールする)
❻ ユーザーが指を一度離すとスクロールが不可になる
という感じで、一度touchendが発生しないとtouch-actionの変更が有効にならないようです。
❌ widow.scrollToと同時にtouchmoveのpriventDefaultを実行する
setTimeout(() => { window.scrollTo(0, 100); document.body.addEventListener('touchmove', handleTouchMove, { passive: false }); setTimeout(() => { document.body.removeEventListener('touchmove', handleTouchMove); }, 0); }, 1000); function handleTouchMove(evt) { evt.preventDefault(); }
こんなイメージです。
一瞬、touchmoveのpriventDefaultを実行する関数をaddしてスクロールを止め、removeするという作戦です。
これも、touch-actionのときと結果は同じで、ユーザーが一度指を離すまではpriventDefaultが有効になりませんでした。
⭕️ 画面と同じ大きさのoverflow: scrollの要素を作り、widow.scrollToと同時にremoveChildする
こうなったら一度window自体をremoveするしかないと思ったのですが、そんなことはできません。
なので、
❶ windowと同じ大きさのoverflow: scrollの要素をつくり、position: fixedで配置(以下、overflowと呼ぶ)
❷ window.scrollToを使わず、overflowのscrollTopを操作
❸ scrollTopを操作したいタイミングでoverflowを一度removeChildし、appendChildしなおす
❹ ❸のappendChildの際にscrollTopを代入
という流れで、一瞬チラつきはしますがほぼほぼAndroidの挙動と同様になりました。長い戦いでした。