深入理解使用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 相关文章推荐
js模拟实现Array的sort方法
Dec 11 Javascript
javascript loadScript异步加载脚本示例讲解
Nov 14 Javascript
js中top的作用深入剖析
Mar 04 Javascript
jQuery常用知识点总结以及平时封装常用函数
Feb 23 Javascript
javascript中不易分清的slice,splice和split三个函数
Mar 29 Javascript
jQuery简单注册和禁用全局事件的方法
Jul 25 Javascript
JS设置时间无效问题的解决办法
Feb 18 Javascript
浅谈JS 数字和字符串之间相互转化的纠纷
Oct 20 Javascript
深入浅析Vue中的slots/scoped slots
Apr 03 Javascript
React props和state属性的具体使用方法
Apr 12 Javascript
移动端 Vue+Vant 的Uploader 实现上传、压缩、旋转图片功能
Jun 10 Javascript
react实现复选框全选和反选组件效果
Aug 25 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 多个submit提交表单 处理方法
2009/07/07 PHP
php绘制一个扇形的方法
2015/01/24 PHP
php随机抽奖实例分析
2015/03/04 PHP
php实现TCP端口检测的方法
2015/04/01 PHP
thinkPHP自定义类实现方法详解
2016/11/30 PHP
理解Javascript闭包
2013/11/01 Javascript
Jquery获取和修改img的src值的方法
2014/02/17 Javascript
一个实用的图片切换支持点击切换和自动轮播
2014/09/09 Javascript
JavaScript中window.open用法实例详解
2015/04/15 Javascript
常用DOM整理
2015/06/16 Javascript
基于JavaScript的操作系统你听说过吗?
2016/01/28 Javascript
利用AJAX实现WordPress中的文章列表及评论的分页功能
2016/05/17 Javascript
微信+angularJS的SPA应用中用router进行页面跳转,jssdk校验失败问题解决
2016/09/09 Javascript
详解基于Angular4+ server render(服务端渲染)开发教程
2017/08/28 Javascript
vue 系列——vue2-webpack2框架搭建踩坑之路
2017/12/22 Javascript
vue2.0 axios跨域并渲染的问题解决方法
2018/03/08 Javascript
在Vue项目中使用d3.js的实例代码
2018/05/01 Javascript
vue中如何实现pdf文件预览的方法
2018/07/12 Javascript
vue组件表单数据回显验证及提交的实例代码
2018/08/30 Javascript
JavaScript使用canvas绘制随机验证码
2020/02/17 Javascript
JavaScript设计模式之门面模式原理与实现方法分析
2020/03/09 Javascript
[01:28]国服启动器接入蒸汽平台操作流程视频
2021/03/11 DOTA
简单理解Python中基于生成器的状态机
2015/04/13 Python
深入浅析ImageMagick命令执行漏洞
2016/10/11 Python
Python 实现异步调用函数的示例讲解
2018/10/14 Python
python itchat给指定联系人发消息的方法
2019/06/11 Python
TensorFlow2.X使用图片制作简单的数据集训练模型
2020/04/08 Python
HTML5 Canvas 实现K线图的示例代码
2019/12/23 HTML / CSS
自荐信格式的六要素
2013/09/21 职场文书
社会实践评语
2014/04/28 职场文书
先进教师事迹材料
2014/12/16 职场文书
学校教师师德师风承诺书
2015/04/28 职场文书
社区法制宣传日活动总结
2015/05/05 职场文书
高一军训感想
2015/08/07 职场文书
导游词之清晏园
2019/11/22 职场文书
HTML5之高度塌陷问题的解决
2022/06/01 HTML / CSS