深入理解使用Vue實(shí)現(xiàn)Context-Menu的思考與總結(jié)
簡(jiǎn)介
先來(lái)看最終成果:
操作邏輯為:
- 點(diǎn)擊 ... 彈出 context-menu;
- 點(diǎn)擊非 context-menu 區(qū)域,隱藏 context-menu;
- 點(diǎn)擊 context-menu 中的任何一個(gè)選項(xiàng),隱藏 context-menu;
思考
項(xiàng)目是基于 vux 做的,本想著偷懶直接在 vux 庫(kù)翻組件用,但看了一圈下來(lái),居然這么通用的組件在 vuex 中沒(méi)有!接著又去翻開(kāi)源的解決方案,看了幾個(gè)庫(kù)還算 ok,但此時(shí)前端小哥來(lái)了,說(shuō)實(shí)現(xiàn)這個(gè)菜單不需要用到這么重的東西,直接寫(xiě)就行了。
當(dāng)時(shí)我的腦海中在思考了把 context-menu 封裝成一個(gè) component ,通過(guò)數(shù)據(jù)配置的方式動(dòng)態(tài)拓展菜單選項(xiàng)。但沒(méi)想到前端小哥直接給我干了回來(lái),沒(méi)必要進(jìn)行封裝,這個(gè)組件對(duì)頁(yè)面依賴(lài)性太強(qiáng),就算封裝完了下次也不一定能直接用,PM 的思路又這么清奇。
所以,最后的做法就直接硬上了。
實(shí)現(xiàn)
調(diào)整操作邏輯
該頁(yè)面是一個(gè)通俗意義上的列表展示頁(yè),使用了 vux 的 swipeout 表單組件,給用戶(hù)提供了側(cè)滑操作,需要把原先寫(xiě)好的側(cè)滑功能刪除。
調(diào)整 UI
在調(diào)整 UI 的過(guò)程中我感到了 CSS 滿(mǎn)滿(mǎn)的惡意,當(dāng)然說(shuō)是這么說(shuō),但實(shí)際上還是因?yàn)樘脹](méi)有用而導(dǎo)致的不夠熟悉。非常費(fèi)勁的終于調(diào)整了好了新 UI,此時(shí)已經(jīng)過(guò)去了整整一天了,非常懷念 autoLayout 。
context-mune
在正式開(kāi)始寫(xiě)之前,上文已經(jīng)說(shuō)了我一直在翻開(kāi)源庫(kù),主要是不懂得如何下手去寫(xiě)。距離上一次寫(xiě) vue 已經(jīng)過(guò)去快兩個(gè)月了,而且也沒(méi)搞清楚如何寫(xiě)一個(gè)組件,所以中間有一段時(shí)間浪費(fèi)在了這上。最后的解決思路讓我感到意外:
<div class="more-menu-wrapper"> <ul v-show="item.showOption"> <li>更換分類(lèi)</li> <li>向上移動(dòng)</li> <li>移至頂部</li> <li>取消收藏</li> </ul> </div>
沒(méi)想到使用無(wú)序列表就可以完成了~在 iOS 中,我會(huì)在 UITableView 和 UIStackView 中糾結(jié)。當(dāng)然只有這樣是不行的,當(dāng)又調(diào)整了 UI 后,發(fā)現(xiàn) ... 和 context-menu “融合”在了一起,沒(méi)有設(shè)計(jì)圖中的“懸浮”效果,最后的解決方法是:
.more-wrapper { /* ... */ position: absolute; .more-menu-wrapper { position: relative; /* ... */ } }
當(dāng)繼續(xù)調(diào)整 CSS 時(shí)又發(fā)現(xiàn) context-menu 的會(huì)被其父組件擋住, context-menu 的顯示范圍會(huì)限制于其父組件的顯示高度,最后得知是 overflow 這個(gè)屬性在最底層的父組件中設(shè)置了 overflow: hidden; ,刪除掉,使其為默認(rèn)的 visible 即可顯示為 context-menu 高度溢出的效果。
事件綁定
UI 都調(diào)整完后開(kāi)始綁定事件。因?yàn)橹皇歉脑?UI,并沒(méi)有涉及到多少的新邏輯,所以很快的就寫(xiě)出了以下代碼:
<ul v-show="item.showOption"> <li @click="moveItem(item)">更換分類(lèi)</li> <li @click="moveUp(item)">向上移動(dòng)</li> <li @click="setTop(item)">移至頂部</li> <li @click="deleteItem(item)">取消收藏</li> </ul>
context-menu 的顯示依賴(lài) v-show ,當(dāng)頁(yè)面首次拉取到網(wǎng)絡(luò)數(shù)據(jù)時(shí), data 中對(duì)每個(gè) listData 的 item 新增了 context-menu 顯示隱藏的初始化標(biāo)志位 item.showOption = false ,且在這四個(gè)入口方法中都控制了 item.showOption 的改變:
//... moveUp(item) { item.showOption = false; // ... } //...
刷新頁(yè)面,很愉快的看到了 context-menu 的顯示,但在點(diǎn)擊菜單選項(xiàng)時(shí)沒(méi)有任何反應(yīng)!一開(kāi)始以為是標(biāo)志位的問(wèn)題,但看來(lái)看去沒(méi)有任何問(wèn)題。
本來(lái)想去找前端小哥看一眼,但一直不在工位上,最后問(wèn)了下同組的前端實(shí)習(xí)生,他認(rèn)為是 item.showOption 字段在數(shù)據(jù)更新時(shí)沒(méi)有加上,導(dǎo)致后續(xù)直接讀取時(shí)不存在。
但我其實(shí)一直納悶如果 item.showOption 字段數(shù)據(jù)不存在的話(huà),那第一次的頁(yè)面渲染實(shí)際上是有錯(cuò)誤的。我們兩個(gè)人看了一會(huì)也沒(méi)發(fā)現(xiàn)具體是哪有問(wèn)題,最后只能四處尋找前端小哥,沒(méi)想到他已經(jīng)被封閉起來(lái)做商業(yè)化了......
前端小哥在文件中加上了 debugger 進(jìn)行調(diào)試,發(fā)現(xiàn)進(jìn)入到 moveUp 等一類(lèi)事件時(shí)雖然 item.showOption 被修改成功了,一旦出去事件周期外,又被改回去了。
最后發(fā)現(xiàn),問(wèn)題出在被 冒泡 到了父組件中,調(diào)用了 ... 所綁定的 onMore 事件中,而在 onMore 事件中 item.showOption = true ,所以實(shí)際上是執(zhí)行了 context-menu 和 ... 的兩者所綁定的事件。解決的方法是:
<ul v-show="item.showOption"> <li @click.stop="moveItem(item)">更換分類(lèi)</li> <li @click.stop="moveUp(item)">向上移動(dòng)</li> <li @click.stop="setTop(item)">移至頂部</li> <li @click.stop="deleteItem(item)">取消收藏</li> </ul>
使用 @click.stop 來(lái)阻止冒泡事件。解決完問(wèn)題后,前端小哥還好奇我做 iOS 怎么會(huì)不知道冒泡事件的問(wèn)題,但實(shí)際上在 iOS 中跟前端的思路完全是反過(guò)來(lái)的。iOS 的事件響應(yīng)鏈?zhǔn)侵鸺?jí)傳遞到子組件中,也就是向下傳遞,而不是像前端中的向上傳遞。所以在遇到這個(gè)問(wèn)題時(shí)也就完全沒(méi)有往冒泡的方面去思考。
觸摸其它區(qū)域消失 context-menu
在 iOS 中,我會(huì)直接封裝出一個(gè)帶有 UIWindow 的組件。與 context-menu 有關(guān)的所有操作與主 window 沒(méi)有任何關(guān)系,更別說(shuō)事件穿透了。所以最終我的做法是多加了一個(gè)遮罩層,顯示和隱藏的時(shí)機(jī)與 context-menu 的時(shí)機(jī)保持一致。
最后在我拿著最終的成果去找前端小哥復(fù)查時(shí),他對(duì)這個(gè)做法不滿(mǎn)意,還是覺(jué)得要使用 outside-click 的做法。也就是使用 js 中的事件代理,通過(guò) e.targe 去判斷。最后找到了可以使用 v-outside-click 進(jìn)行。關(guān)于后續(xù)的細(xì)節(jié)就不展開(kāi)了, v-outside-click 的使用也十分簡(jiǎn)潔。
在使用 v-outside-click 這個(gè)庫(kù)的過(guò)程中遇到了一個(gè)比較大的問(wèn)題。v-outside-click 此庫(kù)給我的感覺(jué)是用于單個(gè)組件,而不適用于多個(gè)組件。列表中的每一個(gè) cell 都需要帶上一個(gè)單獨(dú)的 context-menu,如果給每一個(gè) context-menu 都綁上一個(gè)單獨(dú)的 outside-click 事件,一旦用戶(hù)的觸摸范圍不在 context-menu 中,則視圖上的所有 context-menu 都會(huì)響應(yīng)這個(gè) outside-click 事件,列表數(shù)據(jù)一旦多起來(lái),事件響應(yīng)次數(shù)將線(xiàn)性增長(zhǎng)。
這個(gè)問(wèn)題跟前端小哥說(shuō)過(guò)后,他說(shuō)問(wèn)題不大,那就這樣吧~接下來(lái)的問(wèn)題就到了怎么在 outside-click 事件中標(biāo)識(shí)出是哪個(gè) context-menu 需要隱藏呢?剛開(kāi)始就按照了以往的套路,直接使用了如下所示的方式:
<div v-click-outside="onClickOutside(item)"> <!-- ... --> </div>
然后開(kāi)心看到了報(bào)錯(cuò) Binding value must be a function or an object。提示需要傳入一個(gè)方法?!翻了源碼后發(fā)現(xiàn)了這么一段:
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') } // ... }
回過(guò)頭去看之前寫(xiě)的代碼,沒(méi)有問(wèn)題啊!思來(lái)想去還是沒(méi)弄明白,又去找了前端小哥請(qǐng)求幫忙,經(jīng)過(guò)了一番折騰了,他的結(jié)論是這個(gè)庫(kù)應(yīng)該是有問(wèn)題的。最后采取的解決方法是:
<div v-click-outside="onClickOutside"> <p>…</p> <!-- 重點(diǎn) --> <div :id="item.metricId" v-show="item.showOption"> <ul> <li>更換分類(lèi)</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; } }); } } }
通過(guò)設(shè)置 context-menu 的 id 作為標(biāo)識(shí),然后在 v-outside-click 的指令方法中獲取 id,通過(guò)這個(gè) id 去數(shù)據(jù)源中找到對(duì)應(yīng)的 item,從而設(shè)置 item.showOption = false 來(lái)隱藏 context-menu。
總結(jié)
這算是轉(zhuǎn)大前端完成的第一個(gè)功能吧,因?yàn)椴皇煜?dǎo)致中間出現(xiàn)了一些好玩的事情??蛻?hù)端和前端的開(kāi)發(fā)流程說(shuō)大也不大,但要是說(shuō)沒(méi)有是絕對(duì)不可能的。在一些小的問(wèn)題上,沒(méi)有踩過(guò)坑或者沒(méi)有大佬帶一帶,真的會(huì)爬不起來(lái)或者就棄坑了,說(shuō)到底其實(shí)還是需要多加學(xué)習(xí)??!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue?cli2?和?cli3去掉eslint檢查器報(bào)錯(cuò)的解決
這篇文章主要介紹了vue?cli2?和?cli3去掉eslint檢查器報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04vue實(shí)現(xiàn)拖動(dòng)調(diào)整左右兩側(cè)容器大小
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)拖動(dòng)調(diào)整左右兩側(cè)容器大小,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03vue前端框架vueuse的useScroll函數(shù)使用源碼分析
這篇文章主要為大家介紹了vueuse的useScroll函數(shù)源碼分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08解決iview多表頭動(dòng)態(tài)更改列元素發(fā)生的錯(cuò)誤的方法
這篇文章主要介紹了解決iview多表頭動(dòng)態(tài)更改列元素發(fā)生的錯(cuò)誤的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11vue相同路由跳轉(zhuǎn)強(qiáng)制刷新該路由組件操作
這篇文章主要介紹了vue相同路由跳轉(zhuǎn)強(qiáng)制刷新該路由組件操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08