背景
PC や Android など、iOS 以外のデバイスで、ページ全体をスクロール無効にし、それ以外の要素は有効にしたい場合は、以下の CSS を追加すれば良いです。
(中略)
しかし、iOS では、この方法だとうまくいきません。
実現したいこと
ページ全体をスクロール禁止にした上で、スクロール可能な要素についてのみ内部の(子要素を含む)どこをタッチしてもスクロールする。
overflow:scrollに設定されていても子要素が少なくスクロール可能状態になっていない要素や、スクロール可能状態だが一番下までスクロールされきっており
(諸事情によりwindowやbodyではなく、自分で作成した一番上位の要素をrootElementとして、これにイベントを設定する仕様になっている)
実現方法
- 再帰的に親要素を辿ってスクロール可能要素があるか確認する関数を作成
constcheck=(elem:HTMLElement,rootID:string):boolean=>{elem.scrollBy({top:1});if(elem?.id==rootID){returnfalse;}elseif(elem.scrollTop){if(elem.scrollTop>1)elem.scrollBy({top:-1});returntrue;}elseif(elem.parentElement){returncheck(elem.parentElement);}else{returnfalse;}};
- スクロール可能な要素がなければpreventDefaultする関数を作成
constpreventScroll(rootElement:HtmlElement)=>{rootElement.addEventListener('touchmove',e=>{constelem=e.targetasHTMLElement;if(!(elem&&check(elem,rootElement.id))&&e.cancelable)e.preventDefault();},{passive:false});});
- スクロールを禁止したい要素に適用(ここでは仮称globalContainerに適用)
constroot=document.getElementById("globalContainer");if(root)preventScroll(root)
備考
check()内で一度スクロールを試みた後、スクロール可能な要素と判定された場合最初にスクロールを試みた分を戻しているが、
constcheck=(elem:HTMLElement,rootID:string):boolean=>{elem.scrollBy({top:1}); (中略)elseif(elem.scrollTop){if(elem.scrollTop>1)elem.scrollBy({top:-1});returntrue;}
これによりスクロール可能要素が最後までスクロールしきった状態にならず、スクロールしきった要素を引っ張って画面全体がスクロールされる仕様に対処している。
まとめ
力押しの判定方法で目標を実現した。
また、e.targetをHTMLElementにキャストしている部分や備考で示した箇所の記述など、確実に正しい記述である自信がない部分も多いため、指摘歓迎。