深入理解使用Vue实现Context-Menu的思考与总结


Posted in Javascript onMarch 09, 2019

简介

先来看最终成果:

深入理解使用Vue实现Context-Menu的思考与总结

操作逻辑为:

  • 点击 ... 弹出 context-menu;
  • 点击非 context-menu 区域,隐藏 context-menu;
  • 点击 context-menu 中的任何一个选项,隐藏 context-menu;

思考

项目是基于 vux 做的,本想着偷懒直接在 vux 库翻组件用,但看了一圈下来,居然这么通用的组件在 vuex 中没有!接着又去翻开源的解决方案,看了几个库还算 ok,但此时前端小哥来了,说实现这个菜单不需要用到这么重的东西,直接写就行了。

当时我的脑海中在思考了把 context-menu 封装成一个 component ,通过数据配置的方式动态拓展菜单选项。但没想到前端小哥直接给我干了回来,没必要进行封装,这个组件对页面依赖性太强,就算封装完了下次也不一定能直接用,PM 的思路又这么清奇。

所以,最后的做法就直接硬上了。

实现

调整操作逻辑

该页面是一个通俗意义上的列表展示页,使用了 vux 的 swipeout 表单组件,给用户提供了侧滑操作,需要把原先写好的侧滑功能删除。

调整 UI

在调整 UI 的过程中我感到了 CSS 满满的恶意,当然说是这么说,但实际上还是因为太久没有用而导致的不够熟悉。非常费劲的终于调整了好了新 UI,此时已经过去了整整一天了,非常怀念 autoLayout 。

context-mune

在正式开始写之前,上文已经说了我一直在翻开源库,主要是不懂得如何下手去写。距离上一次写 vue 已经过去快两个月了,而且也没搞清楚如何写一个组件,所以中间有一段时间浪费在了这上。最后的解决思路让我感到意外:

<div class="more-menu-wrapper">
 <ul v-show="item.showOption">
  <li>更换分类</li>
  <li>向上移动</li>
  <li>移至顶部</li>
  <li>取消收藏</li>
 </ul>
</div>

没想到使用无序列表就可以完成了~在 iOS 中,我会在 UITableView 和 UIStackView 中纠结。当然只有这样是不行的,当又调整了 UI 后,发现 ... 和 context-menu “融合”在了一起,没有设计图中的“悬浮”效果,最后的解决方法是:

.more-wrapper {
 /* ... */
 position: absolute;
 .more-menu-wrapper {
  position: relative;
  /* ... */
 }
}

当继续调整 CSS 时又发现 context-menu 的会被其父组件挡住, context-menu 的显示范围会限制于其父组件的显示高度,最后得知是 overflow 这个属性在最底层的父组件中设置了 overflow: hidden; ,删除掉,使其为默认的 visible 即可显示为 context-menu 高度溢出的效果。

事件绑定

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 中对每个 listData 的 item 新增了 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-menu 的 id 作为标识,然后在 v-outside-click 的指令方法中获取 id,通过这个 id 去数据源中找到对应的 item,从而设置 item.showOption = false 来隐藏 context-menu。

总结

这算是转大前端完成的第一个功能吧,因为不熟悉导致中间出现了一些好玩的事情。客户端和前端的开发流程说大也不大,但要是说没有是绝对不可能的。在一些小的问题上,没有踩过坑或者没有大佬带一带,真的会爬不起来或者就弃坑了,说到底其实还是需要多加学习啊!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用CSS+JS实现的进度条效果效果
Jun 05 Javascript
js写一个字符串转成驼峰的实例
Jun 21 Javascript
Javascript无阻塞加载具体方式
Jun 28 Javascript
js判断浏览器类型的方法
Aug 07 Javascript
基于jquery的文字向上跑动类似跑马灯的效果
Sep 22 Javascript
基于JavaScript实现通用tab选项卡(通用性强)
Jan 07 Javascript
js实现图片缓慢放大缩小效果
Aug 02 Javascript
AngularJS控制器之间的通信方式详解
Nov 03 Javascript
JS常见构造模式实例对比分析
Aug 27 Javascript
vue中轮训器的使用
Jan 27 Javascript
vue element自定义表单验证请求后端接口验证
Dec 11 Javascript
JS数组的高级使用方法示例小结
Mar 14 Javascript
vue模块拖拽实现示例代码
Mar 09 #Javascript
Vue中的验证登录状态的实现方法
Mar 09 #Javascript
在NPM发布自己造的轮子的方法步骤
Mar 09 #Javascript
使用pm2部署node生产环境的方法步骤
Mar 09 #Javascript
Koa日志中间件封装开发详解
Mar 09 #Javascript
详解vue2.6插槽更新v-slot用法总结
Mar 09 #Javascript
Node.js Stream ondata触发时机与顺序的探索
Mar 08 #Javascript
You might like
生成静态页面的php函数,php爱好者站推荐
2007/03/19 PHP
laravel5表单唯一验证的实例代码
2019/09/30 PHP
js判断变量是否未定义的代码
2020/03/28 Javascript
浏览器打开层自动缓慢展开收缩实例代码
2013/07/04 Javascript
js如何取消事件冒泡
2013/09/23 Javascript
js读取被点击次数的简单实例(从数据库中读取)
2014/03/07 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
2015/10/16 Javascript
JS基于clipBoard.js插件实现剪切、复制、粘贴
2016/05/03 Javascript
javascript面向对象程序设计高级特性经典教程(值得收藏)
2016/05/19 Javascript
基于jQuery实现Accordion手风琴自定义插件
2020/10/13 Javascript
jQuery实现表格与ckeckbox的全选与单选功能
2016/11/24 Javascript
JS获取当前地理位置的方法
2017/10/25 Javascript
vue实现底部菜单功能
2018/07/24 Javascript
20多个小事例带你重温ES10新特性(小结)
2019/09/29 Javascript
Vue实现Header渐隐渐现效果的实例代码
2020/11/05 Javascript
[02:58]魔廷新尊——痛苦女王至宝语音台词节选
2020/06/14 DOTA
[01:01:41]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Magma BO3 第二场 1月31日
2021/03/11 DOTA
python实现定制交互式命令行的方法
2014/07/03 Python
python3.6使用SMTP协议发送邮件
2020/05/20 Python
Opencv求取连通区域重心实例
2020/06/04 Python
Python常用类型转换实现代码实例
2020/07/28 Python
localStorage的过期时间设置的方法详解
2018/11/26 HTML / CSS
canvas实现烟花的示例代码
2020/01/16 HTML / CSS
德国化妆品和天然化妆品网上商店:kosmetikfuchs.de
2017/06/09 全球购物
采购主管的岗位职责
2013/12/17 职场文书
前台文员岗位职责
2013/12/28 职场文书
先进班级集体事迹材料
2014/01/30 职场文书
运动会跳远广播稿
2014/02/04 职场文书
《我为你骄傲》教学反思
2014/02/20 职场文书
《伯牙绝弦》教学反思
2014/03/02 职场文书
市级三好学生评语
2014/12/29 职场文书
房产销售员2015年终工作总结
2015/10/22 职场文书
小学思想品德教学反思
2016/02/24 职场文书
Navicat连接MySQL错误描述分析
2021/06/02 MySQL
MySQL系列之九 mysql查询缓存及索引
2021/07/02 MySQL
《辉夜大小姐想让我告白》第三季正式预告
2022/03/20 日漫