使用 Vue 實現 Context-Menu 的思考與總結

簡介

先來看最終成果:css

context-menu

操做邏輯爲:html

  • 點擊 ... 彈出 context-menu;
  • 點擊非 context-menu 區域,隱藏 context-menu
  • 點擊 context-menu 中的任何一個選項,隱藏 context-menu

思考

項目是基於 vux 作的,本想着偷懶直接在 vux 庫翻組件用,但看了一圈下來,竟然這麼通用的組件在 vuex 中沒有!接着又去翻開源的解決方案,看了幾個庫還算 ok,但此時前端小哥來了,說實現這個菜單不須要用到這麼重的東西,直接寫就好了。前端

當時個人腦海中在思考了把 context-menu 封裝成一個 component,經過數據配置的方式動態拓展菜單選項。但沒想到前端小哥直接給我幹了回來,不必進行封裝,這個組件對頁面依賴性太強,就算封裝完了下次也不必定能直接用,PM 的思路又這麼清奇。vue

因此,最後的作法就直接硬上了。git

實現

調整操做邏輯

該頁面是一個通俗意義上的列表展現頁,使用了 vuxswipeout 表單組件,給用戶提供了側滑操做,須要把原先寫好的側滑功能刪除。github

調整 UI

在調整 UI 的過程當中我感到了 CSS 滿滿的惡意,固然說是這麼說,但實際上仍是由於過久沒有用而致使的不夠熟悉。很是費勁的終於調整了好了新 UI,此時已通過去了整整一天了,很是懷念 autoLayoutvuex

context-mune

在正式開始寫以前,上文已經說了我一直在翻開源庫,主要是不懂得如何下手去寫。距離上一次寫 vue 已通過去快兩個月了,並且也沒搞清楚如何寫一個組件,因此中間有一段時間浪費在了這上。最後的解決思路讓我感到意外:網絡

<div class="more-menu-wrapper">
    <ul v-show="item.showOption">
        <li>更換分類</li>
        <li>向上移動</li>
        <li>移至頂部</li>
        <li>取消收藏</li>
    </ul>
</div>
複製代碼

沒想到使用無序列表就能夠完成了~在 iOS 中,我會在 UITableViewUIStackView 中糾結。固然只有這樣是不行的,當又調整了 UI 後,發現 ...context-menu 「融合」在了一塊兒,沒有設計圖中的「懸浮」效果,最後的解決方法是:app

.more-wrapper {
    /* ... */
    position: absolute;
    .more-menu-wrapper {
        position: relative;
        /* ... */
    }
}
複製代碼

當繼續調整 CSS 時又發現 context-menu 的會被其父組件擋住,context-menu 的顯示範圍會限制於其父組件的顯示高度,最後得知是 overflow 這個屬性在最底層的父組件中設置了 overflow: hidden;,刪除掉,使其爲默認的 visible 便可顯示爲 context-menu 高度溢出的效果。ide

事件綁定

UI 都調整完後開始綁定事件。由於只是改造 UI,並無涉及到多少的新邏輯,因此很快的就寫出瞭如下代碼:

<ul v-show="item.showOption">
    <li @click="moveItem(item)">更換分類</li>
    <li @click="moveUp(item)">向上移動</li>
    <li @click="setTop(item)">移至頂部</li>
    <li @click="deleteItem(item)">取消收藏</li>
</ul>
複製代碼

context-menu 的顯示依賴 v-show,當頁面首次拉取到網絡數據時,data 中對每一個 listDataitem 新增了 context-menu 顯示隱藏的初始化標誌位 item.showOption = false,且在這四個入口方法中都控制了 item.showOption 的改變:

//...
moveUp(item) {
    item.showOption = false;
    // ...
}
//...
複製代碼

刷新頁面,很愉快的看到了 context-menu 的顯示,但在點擊菜單選項時沒有任何反應!一開始覺得是標誌位的問題,但看來看去沒有任何問題。

原本想去找前端小哥看一眼,但一直不在工位上,最後問了下同組的前端實習生,他認爲是 item.showOption 字段在數據更新時沒有加上,致使後續直接讀取時不存在。

但我其實一直納悶若是 item.showOption 字段數據不存在的話,那第一次的頁面渲染其實是有錯誤的。咱們兩我的看了一會也沒發現具體是哪有問題,最後只能四處尋找前端小哥,沒想到他已經被封閉起來作商業化了......

前端小哥在文件中加上了 debugger 進行調試,發現進入到 moveUp 等一類事件時雖然 item.showOption 被修改爲功了,一旦出去事件週期外,又被改回去了。

最後發現,問題出在被冒泡到了父組件中,調用了 ... 所綁定的 onMore 事件中,而在 onMore 事件中 item.showOption = true,因此其實是執行了 context-menu... 的二者所綁定的事件。解決的方法是:

<ul v-show="item.showOption">
    <li @click.stop="moveItem(item)">更換分類</li>
    <li @click.stop="moveUp(item)">向上移動</li>
    <li @click.stop="setTop(item)">移至頂部</li>
    <li @click.stop="deleteItem(item)">取消收藏</li>
</ul>
複製代碼

使用 @click.stop 來阻止冒泡事件。解決完問題後,前端小哥還好奇我作 iOS 怎麼會不知道冒泡事件的問題,但實際上在 iOS 中跟前端的思路徹底是反過來的。iOS 的事件響應鏈是逐級傳遞到子組件中,也就是向下傳遞,而不是像前端中的向上傳遞。因此在遇到這個問題時也就徹底沒有往冒泡的方面去思考。

觸摸其它區域消失 context-menu

在 iOS 中,我會直接封裝出一個帶有 UIWindow 的組件。與 context-menu 有關的全部操做與主 window 沒有任何關係,更別說事件穿透了。因此最終個人作法是多加了一個遮罩層,顯示和隱藏的時機與 context-menu 的時機保持一致。

最後在我拿着最終的成果去找前端小哥複查時,他對這個作法不滿意,仍是以爲要使用 outside-click 的作法。也就是使用 js 中的事件代理,經過 e.targe 去判斷。最後找到了可使用 v-outside-click 進行。v-outside-click 有兩種引入的方式,爲了簡潔,我選擇了「指令」的方式引入。

在使用 v-outside-click 這個庫的過程當中遇到了一個比較大的問題。v-outside-click 此庫給個人感受是用於單個組件,而不適用於多個組件。列表中的每個 cell 都須要帶上一個單獨的 context-menu,若是給每個 context-menu 都綁上一個單獨的 outside-click 事件,一旦用戶的觸摸範圍不在 context-menu 中,則視圖上的全部 context-menu 都會響應這個 outside-click 事件,列表數據一旦多起來,事件響應次數將線性增加。

這個問題跟前端小哥說事後,他說問題不大,那就這樣吧~接下來的問題就到了怎麼在 outside-click 事件中標識出是哪一個 context-menu 須要隱藏呢?剛開始就按照了以往的套路,直接使用了以下所示的方式:

<div v-click-outside="onClickOutside(item)">
    <!-- ... -->
</div>
複製代碼

而後開心看到了報錯 Binding value must be a function or an object。提示須要傳入一個方法?!翻了源碼後發現了這麼一段:

function processDirectiveArguments(bindingValue) {
  const isFunction = typeof bindingValue === 'function'
  if (!isFunction && typeof bindingValue !== 'object') {
    throw new Error('v-click-outside: Binding value must be a function or an object')
  }

  // ...
}
複製代碼

回過頭去看以前寫的代碼,沒有問題啊!思來想去仍是沒弄明白,又去找了前端小哥請求幫忙,通過了一番折騰了,他的結論是這個庫應該是有問題的。最後採起的解決方法是:

<div v-click-outside="onClickOutside">
    <p></p>
    <!-- 重點 -->
    <div :id="item.metricId" v-show="item.showOption">
        <ul>
            <li>更換分類</li>
            <!-- ... -->
        </ul>
    </div>
</div>
複製代碼
onClickOutside (event, el) {
    let queryInstance = el.querySelector('.more-menu-wrapper')         
    if (queryInstance) {
        let metricId = el.querySelector('.more-menu-wrapper').id;
        if (metricId != "") {
            this.listData.some((item) => {
                if (item.metricId == metricId) {
                    item.showOption = false;
                    return true;
                }
            });
        }
    }   
}
複製代碼

經過設置 context-menuid 做爲標識,而後在 v-outside-click 的指令方法中獲取 id,經過這個 id 去數據源中找到對應的 item,從而設置 item.showOption = false 來隱藏 context-menu

總結

這算是轉大前端完成的第一個功能吧,由於不熟悉致使中間出現了一些好玩的事情。客戶端和前端的開發流程說大也不大,但要是說沒有是絕對不可能的。在一些小的問題上,沒有踩過坑或者沒有大佬帶一帶,真的會爬不起來或者就棄坑了,說到底其實仍是須要多加學習啊!

原文地址:PJ 的 iOS 開發之路

相關文章
相關標籤/搜索