1. 程式人生 > >【js】再談移動端的模態框實現

【js】再談移動端的模態框實現

其中 這就是 層級關系 成了 移動 top 做了 rop 操作

  移動端模態框的機制因為與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】再談移動端的模態框實現