手写实现vue2下拉菜单dropdown组件实例
概述
一般后台项目结合分页组件用到这个dropdown组件,用来做显示分页条数,其他用到这个组件的地方倒不是很多,其实现思路和select组件有那么些相似,现记录下这个组件的实现。
最终效果(动图没显示出来,请稍定会儿,可以先看后面)
实现原理
这个组件和select组件记起相似,可以参考我之前的文章【手写vue2select下拉组件】,要做这个组件,需要注意以下几点:
组件分为两部分:
- 供我们点击的文字,按钮,超链接等等(当成插槽供用户提供)
- 下拉菜单项(支持边框,禁用等)
使用该组件应当提供的事件应该是点击item项,然后将对应的item的对应value暴露出来,供用户使用。
组件菜单项的显示隐藏需要过渡动画。
默认菜单项方向向下,当下方可视区的高度不足以容纳下拉菜单的高度的时候,自动让菜单方向向上。
具体实现
目录结构
emitter.js
这个在之前的组件实现过程中介绍过这个文件,主要是为了解决跨多层级父子组件之前数据通信的,本质上实现原理为发布订阅模式。
/** * @Description 由于涉及到跨组件之间通信,因此我们只有自己实现发布订阅的模式,来实现组件之间通信,灵感主要来源于element-ui组件库源码中跨层级父子组件通信方案,本质上也是发布订阅和$emit和$on * @param { String } componentName 组件名 * @param { String } eventName 事件名 * @param { argument } params 参数 **/ // 广播通知事件 function _broadcast(componentName, eventName, params) { // 遍历当前组件的子组件 this.$children.forEach(function (child) { // 取出componentName,组件options上面可以自己配置 var name = child.$options.componentName; // 如果找到了需要通知的组件名,触发组件上面的$eimit方法,触发自定义事件 if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { // 没找到,递归往下找 _broadcast.apply(child, [componentName, eventName].concat([params])); } }); } const emiiter = { methods: { // 派发事件(通知父组件) dispatch(componentName, eventName, params) { var parent = this.$parent || this.$root; var name = parent.$options.componentName; // 循环往上层父组件,知道知道组件名和需要触发的组件名相同即可,然后触发对应组件的事件 while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, // 广播事件(通知子组件) broadcast(componentName, eventName, params) { _broadcast.call(this, componentName, eventName, params); }, }, }; export default emiiter;
MyDropdown.vue
主要暴露给用户使用的组件
<template> <div class="my-dropdown" @click.stop="trigger == 'click' ? (showMenu = !showMenu) : ''" @mouseenter="trigger == 'hover' ? (showMenu = true) : ''" @mouseleave="trigger == 'hover' ? (showMenu = false) : ''" ref="myDropDdown" > <div class="tip-text" ref="tipText"> <slot></slot> </div> <slot name="list"></slot> </div> </template> <script> import emitter from "./emitter"; export default { name: "MyDropdown", componentName: "MyDropdown", mixins: [emitter], props: { // 触发显示方式 trigger: { type: String, default: "click", }, // 下来菜单的出现位置(上方,下方) placement: { type: String, default: "bottom", validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ["bottom", "top"].includes(value); }, }, }, data() { return { //控制菜单是否显示 showMenu: false, }; }, mounted() { //初始化自定义事件 this.initEvent(); }, methods: { // 初始化 initEvent() { //订阅当item点击的时候,发布on-click事件,告知外部 this.$on("item-click", (params) => { this.$emit("on-click", params); this.showMenu = false; }); //空白点击要隐藏菜单,需要执行的函数需要绑定this指向 this.handleEmptyDomElementClickBindThis = this.handleEmptyDomElementClick.bind(this); window.addEventListener("click", this.handleEmptyDomElementClickBindThis); }, // 处理空白区域点击,隐藏菜单列表 handleEmptyDomElementClick(e) { if (!Array.from(this.$refs.myDropDdown.childNodes).includes(e.target)) { this.showMenu = false; } }, }, beforeDestroy() { // 移除window上面的事件 window.removeEventListener(this.handleEmptyDomElementClickBindThis); }, watch: { //变化的时候,通知子组件隐藏菜单列表 showMenu() { this.broadcast("MyDropdownMenu", "set-menu-status", this.showMenu); }, }, }; </script> <style lang="less"> .my-dropdown { position: relative; } </style>
MyDropdownMenu.vue
主要暴露给用户使用的组件,菜单列表组件
<template> <!-- 涉及到高度,位移,过渡使用js钩子函数的方式比较好处理些 --> <transition @before-enter="beforeEnter" @enter="enter" @leave="leave" v-bind:css="false" > <div class="my-dropdown-menu" v-if="showMeune" ref="myDroupdownMenu"> <slot></slot> </div> </transition> </template> <script> import emitter from "./emitter"; export default { name: "MyDropdownMenu", componentName: "MyDropdownMenu", mixins: [emitter], data() { return { showMeune: false, timer: null, }; }, mounted() { this.$on("set-menu-status", (status) => { this.showMeune = status; }); }, methods: { //进入前,初始化需要过渡的属性 beforeEnter: function (el) { // 初始化 el.style.opacity = 0; el.style.transform = "scaleY(0)"; el.style.transformOrigin = "top center"; }, //dom进入 enter: function (el, done) { //获取文档可视区高度 const htmlClientHeight = document.documentElement.clientHeight; //菜单列表相对于父元素的top偏移量 const offsetTop = el.offsetTop; const scrollHeight = el.scrollHeight; //获取当前元素和可视区的一些长度(top,left,bottom等) const { bottom } = el.getBoundingClientRect(); // 说明底部高度不够显示菜单了,这时候我们需要调整菜单朝上面显示 if (htmlClientHeight - bottom < scrollHeight) { el.style.transformOrigin = "bottom center"; el.style.top = -(scrollHeight + 20) + "px"; } else { //查看是否placement属性,是的话我们主动处理 if (this.$parent.placement == "top") { el.style.transformOrigin = "bottom center"; el.style.top = -(scrollHeight + 20) + "px"; } else { el.style.top = offsetTop + "px"; } } el.style.transform = "scaleY(1)"; el.style.opacity = 1; //根据官网事例,必须在enter和leave里面调用done函数,不然过渡动画不生效(切记) done(); }, //dom元素离开 leave: function (el, done) { el.style.transform = "scaleY(0)"; el.style.opacity = 0; clearTimeout(this.timer); this.timer = setTimeout(() => { //根据官网事例,必须在enter和leave里面调用done函数,不然过渡动画不生效(切记) done(); }, 250); }, }, }; </script> <style lang="less"> .my-dropdown-menu { min-width: 100px; max-height: 200px; overflow: auto; margin: 5px 0; padding: 5px 0; background-color: #fff; box-sizing: border-box; border-radius: 4px; box-shadow: 0 1px 6px rgb(0 0 0 / 20%); z-index: 900; transform-origin: top center; position: absolute; transition: transform 0.25s ease, opacity 0.25s ease; } </style>
MyDropdownItem.vue
主要暴露给用户使用的组件,菜单列表项组件,组件内容很简单,主要就是展示item数据和绑定点击事件。
<template> <div :class="[ 'my-dropdownItem', divided ? 'my-dropdownItem-divided' : '', disabled ? 'my-dropdownItem-disabled' : '', ]" @click.stop="handleItemClick" > <slot></slot> </div> </template> <script> import emitter from "./emitter"; export default { name: "MyDropdownItem", componentName: "MyDropdownItem", mixins: [emitter], props: { divided: { type: Boolean, default: false, }, disabled: { type: Boolean, default: false, }, name: { type: String, default: "", }, }, data() { return {}; }, methods: { handleItemClick() { if (this.disabled) return; // item项点击通知dropdown组件派发到外部的自定义事件 this.dispatch("MyDropdown", "item-click", this.name); }, }, }; </script> <style lang="less"> .my-dropdownItem { margin: 0; line-height: normal; padding: 7px 16px; clear: both; color: #515a6e; font-size: 14px !important; white-space: nowrap; list-style: none; cursor: pointer; transition: background 0.2s ease-in-out; &:hover { background: #f3f3f3; } } .my-dropdownItem-divided { border-bottom: 1px solid #eee; } .my-dropdownItem-disabled { color: #cacdd2; cursor: not-allowed; &:hover { background: #fff; } } </style>
总结
类似组件库中的这种经常出现跨多层级组件通信,需要特殊处理,一般emitter.js文件里面的封装我们在开发中一般是用不到的,我们写组件经常也就父子组件之间,很少会跨祖孙级别,但是在组件库中,这种关系就很多,因此需要单独利用发布订阅来处理,这种模式用到实际项目里面也是很管用的。
以上就是手写实现vue2下拉菜单dropdown组件实例的详细内容,更多关于vue 下拉菜单dropdown的资料请关注猪先飞其它相关文章!
原文出处:https://juejin.cn/post/7135012436251410439
相关文章
- 这篇文章主要介绍了vue中activated的用法,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2021-01-03
基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能
这篇文章主要介绍了基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-23Antd-vue Table组件添加Click事件,实现点击某行数据教程
这篇文章主要介绍了Antd-vue Table组件添加Click事件,实现点击某行数据教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-17- 这篇文章主要介绍了Vue基于localStorage存储信息代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-16
- 这篇文章主要介绍了vue 实现动态路由的方法,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-06
- 这篇文章主要介绍了vue 监听 Treeselect 选择项的改变操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
- 这篇文章主要介绍了Vue组件跨层级获取组件操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-28
- 本文实例讲述了jQuery实现非常实用漂亮的select下拉菜单选择效果。分享给大家供大家参考,具体如下:先来看如下运行效果截图:在线演示地址如下:http://demo.jb51.net/js/2015/js-select-chose-style-menu-codes/具体代码如...2015-11-08
- 这篇文章主要介绍了vue 获取到数据但却渲染不到页面上的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-19
antdesign-vue结合sortablejs实现两个table相互拖拽排序功能
这篇文章主要介绍了antdesign-vue结合sortablejs实现两个table相互拖拽排序功能,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-09vuejs element table 表格添加行,修改,单独删除行,批量删除行操作
这篇文章主要介绍了vuejs element table 表格添加行,修改,单独删除行,批量删除行操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-18- 这篇文章主要介绍了vue treeselect获取当前选中项的label实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
- 这篇文章主要给大家介绍了关于Vue中slot-scope的深入理解,这个教程非常适合初学者,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-17
- 最常见的多环境配置,就是开发环境配置,和生产环境配置,本文主要介绍了vue项目多环境配置的实现,感兴趣的可以了解一下...2021-07-20
vue项目页面嵌入代码块vue-prism-editor的实现
这篇文章主要介绍了vue项目页面嵌入代码块vue-prism-editor的实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-30- 这篇文章主要介绍了Vue 3.0 中 jsx 语法使用,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2020-11-13
vue Treeselect下拉树只能选择第N级元素实现代码
这篇文章主要介绍了vue Treeselect下拉树只能选择第N级元素实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01解决vue的router组件component在import时不能使用变量问题
这篇文章主要介绍了解决vue的router组件component在import时不能使用变量问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27Ant design vue table 单击行选中 勾选checkbox教程
这篇文章主要介绍了Ant design vue table 单击行选中 勾选checkbox教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-25- 这篇文章主要为大家详细介绍了vue实现同时设置多个倒计时,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-05-20