【js】再談移動端的模態框實現
移動端模態框的機制因為與PC的模態框機制一直有所區別,一直是許多新人很容易踩坑的地方,最近筆者作為一條老鹹魚也踩進了一個新坑中,真是平日裏代碼讀得太粗略,故而寫上幾筆,以儆效尤。
故事的起因是這樣的,兄弟團的童鞋的頁面出現了模塊框內需要滾動元素的需求,但是實際情況是他調試了很久,卻沒有找到確定的解決問題,這也引起了筆者的註意,雖然有現成的組件,但是因為相關代碼是有一些歷史的了,並沒有遷移,於是筆者就和他以前聯調了一番。
我們知道常見的pc端模塊框阻止滾動的方式是在html或者body標簽上添加overflow:hidden,以及margin:0等來實例上將頁面置為一個不可滾動的頁面,而在移動端,則需要我們手動的阻止相關dom的touchmove事件的冒泡,來達到目的,示意代碼如下:
/*html code*/ <div class=‘modal‘> <div class="overlay" id="overlayId"></div> <div class=‘modal-content‘id=‘YourModalContentId‘></div> </div> /*js code*/ function addEvt(dom){ dom && dom.addEventListener(‘touchmove‘, onTouchMove); } function onTouchMove(e){ e.preventDefault(); }function fn(){ let overlayDom = document.querySelector(‘#overlayId‘); let modalDom = document.querySelector(‘#YourModalContentId‘); }
阻止所有手指可以碰觸的元素的touchmove事件冒泡(以免引起比如在微信中的view滾動,也可避免不能觸發click事件,因為click事件不需要touchmove,只需要touchstart和touchend),這是其中的原理,然後實際例子稍微復雜了一點,因為實際場景需要modalcontent內部的dom滾動,一般做法是引入iscroll用touchmove事件來模擬滾動事件,但是這位童鞋做了常規操作之後得到了不同的結論,裏面的dom依然不能滾動,經過筆者和他仔細的比對之後,發現基本上只有一行代碼的不同:
/*html code*/ <div class=‘modal‘> <div class="overlay" id="overlayId"></div> <div class=‘modal-content‘id=‘YourModalContentId‘> <ul> ... </ul> </div> </div> /*js code*/ function addEvt(dom){ dom && dom.addEventListener(‘touchmove‘, onTouchMove); } function onTouchMove(e){ e.preventDefault(); e.stopPropagation(); } function fn(){ let overlayDom = document.querySelector(‘#overlayId‘); let modalDom = document.querySelector(‘#YourModalContentId‘); let scroller = new IScroll(modalDom, YourOptions); }
就是上面標紅的那句話,但是正常情況下stopPropagation才是阻止事件冒泡,筆者開始也以為應該是沒有關系的,但是經過反復測試後發現。。沒有那句話,內部dom的滾動沒有任何問題,但是有了那句話之後,內部則不能滾動了。。細細思考之後,筆者覺著。。多半是iscroll內部的實現機制了。。
於是讀了下iscroll的源碼,發現iscroll在initEvents時做了一個神奇的操作:
_initEvents: function(remove) { var eventType = remove ? utils.removeEvent : utils.addEvent, target = this.options.bindToWrapper ? this.wrapper : window; eventType(window, ‘orientationchange‘, this); eventType(window, ‘resize‘, this); if (this.options.click) { eventType(this.wrapper, ‘click‘, this, true); } if (!this.options.disableMouse) { eventType(this.wrapper, ‘mousedown‘, this); eventType(target, ‘mousemove‘, this); eventType(target, ‘mousecancel‘, this); eventType(target, ‘mouseup‘, this); } if (utils.hasPointer && !this.options.disablePointer) { eventType(this.wrapper, ‘MSPointerDown‘, this); eventType(target, ‘MSPointerMove‘, this); eventType(target, ‘MSPointerCancel‘, this); eventType(target, ‘MSPointerUp‘, this); } if (utils.hasTouch && !this.options.disableTouch) { eventType(this.wrapper, ‘touchstart‘, this); eventType(target, ‘touchmove‘, this); eventType(target, ‘touchcancel‘, this); eventType(target, ‘touchend‘, this); } eventType(this.scroller, ‘transitionend‘, this); eventType(this.scroller, ‘webkitTransitionEnd‘, this); eventType(this.scroller, ‘oTransitionEnd‘, this); eventType(this.scroller, ‘MSTransitionEnd‘, this); }
在適用方沒有強制綁定wrapper的情況下,touchstart、touchmove、touchend的target都是window!看到這裏聰明的你也許已經反應過來了,這就是為什麽我們平常寫到touch事件的代碼在移動出了dom的範圍之後不能正常的釋放,而iscroll的可以。。因為除了touchstart之外,其他的事件都是加在全局的window對象上的,而我們遇到的這個實際問題又恰恰使用了touchmove事件,事件觸發的層級關系變成了:
/*dom 示意*/ window //iscroll 處理touchmove -html -body --modal ---overlay //阻止事件冒泡 ---modal-content //阻止事件冒泡 ----iScrollElement
我們需要等待事件冒泡到了window上,才能正常的處理iscrollElement的touchmove行為,看到這裏。。筆者內心也是深感“這是一個何其大的大烏龍啊”。。不過也是因為平日中太過偏重解決問題,而沒有仔細研究解決問題的方法的原理與機制。
雖然各司其職是現代化大分工的基本訴求,但是有的時候知其所以然才能更有價值的提高我們的工作效率,對於我們解決實際問題,也是頗有裨益的。
【js】再談移動端的模態框實現