手写实现vue2下拉菜单dropdown组件实例

目录
  • 概述
  • 最终效果(动图没显示出来,请稍定会儿,可以先看后面)
  • 实现原理
  • 具体实现
    • 目录结构
    • emitter.js
    • MyDropdown.vue
    • MyDropdownMenu.vue
    • MyDropdownItem.vue
  • 总结

概述

一般后台项目结合分页组件用到这个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的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue+element实现下拉菜单并带本地搜索功能示例详解

    需求: 后台返回数组对像,前端组合成数组,根据name组合成一个个数组并把后台返回的值当成一个children推入数组,在数组中自定义属性备份数据防止搜索的时候改变原数组使得数组无法回退 这里是用的vuex存储,因为多个页面使用同一个接口;所以没必要重复请请求 src\store\module\metadata.js /* * @Author: your name * @Date: 2021-09-02 15:46:45 * @LastEditTime: 2021-09-16 17:39:53

  • Vue+Element UI实现下拉菜单的封装

    本文实例为大家分享了Vue+Element UI实现下拉菜单封装的具体代码,供大家参考,具体内容如下 1.效果图 先贴个效果图,菜单项没有做样式美化,图中显示的边框也是没有的(边框是外部容器的边框),其它的根据需要自己修改一下样式即可. 2.组件封装 组件的封装用到了CSS动画.定位.,以及Element UI提供的下拉菜单组件el-dropdown.代码如下, <template> <div class="all" @click="clickFire&qu

  • 解决vue动态下拉菜单 有数据未反应的问题

    问题出现在当时后台数据会返回到data中但是没有出现下拉菜单,查询资料 发现 Vue的this理解有误 jsp 下拉菜单 <select name="plantModelParentId" v-model="vueObj.plantModelParentId" @change="selectChange"> <option value=""></option> <option v-fo

  • vue3.0实现下拉菜单的封装

    vue3.0出来已经有段时间的了,也与必要开始研究它了! 先看下我们要实现的效果 很常见的展开显示菜单项的内容,在vue3.0里面怎么开发,这里样式我们用的是bootstrap的默认样式 思路一: <DropDown :title="'退出'" :list="menuLists" /> 思路二: <drop-down :title="'退出'"> <drop-dowm-item>新建文章</drop-do

  • Vue下拉菜单组件化开发详解

    本文实例为大家分享了Vue下拉菜单组件化开发的具体代码,供大家参考,具体内容如下 搞一个自定义组件,只是一个很简单的下拉菜单,也就是一个思路,整起 第一步:在项目中专门创建一个放置自定义组件的文件夹(直接components底下的common中) dropdown.vue 为一级盒子 dropdownMenu.vue 为二级盒子 dropdownItem.vue 为二级盒子内容 第二步 : 对dropdown.vue的操作 <template> <div class="box&

  • vue实现下拉菜单树

    本文实例为大家分享了vue实现下拉菜单树的具体代码,供大家参考,具体内容如下 效果:使用 Vue-Treeselect 实现 建议通过npm安装vue-treeselect,并使用webpack之类的捆绑器来构建您的应用程序. npm install --save @riophae/vue-treeselect 官网实例 配置属性请查看官网 <!-- Vue SFC --> <template> <div id="app"> <treesele

  • 手写实现vue2下拉菜单dropdown组件实例

    目录 概述 最终效果(动图没显示出来,请稍定会儿,可以先看后面) 实现原理 具体实现 目录结构 emitter.js MyDropdown.vue MyDropdownMenu.vue MyDropdownItem.vue 总结 概述 一般后台项目结合分页组件用到这个dropdown组件,用来做显示分页条数,其他用到这个组件的地方倒不是很多,其实现思路和select组件有那么些相似,现记录下这个组件的实现. 最终效果(动图没显示出来,请稍定会儿,可以先看后面) 实现原理 这个组件和select组

  • Bootstrap 下拉菜单.dropdown的具体使用方法

    本章将具体讲解下拉菜单的交互.使用下拉菜单(Dropdown)插件,您可以向任何组件(比如导航栏.标签页.胶囊式导航菜单.按钮等)添加下拉菜单. 下拉菜单.dropdown具体用法 .dropdown <下拉菜单触发器button+下拉菜单ul> .dropdown 包裹层 .dropdown-toggle 下拉菜单触发器 data-toggle="dropdown  自定义属性 可以与js关联起来 .dropdown-menu  下拉菜单 具体实例: <div class=&

  • Android仿美团下拉菜单(商品选购)实例代码

    美团电商应用平台大家使用非常频繁,下面小编通过本文给大家介绍电商应用平台中常用的选择类别下拉列表的实现.先给大家展示下效果图: 一.下拉列表的实现 其实实现方法有很多,这时实现的也没有什么技术含量,只是总结下自己在项目中的做法,也提供一个思路. 首先是列表的数据,一般数据都是从后台读过来,这里因为没有后台,所以写死在客户端: private void initMenuData() { menuData = new ArrayList<map<string, string=""

  • jquery 无限极下拉菜单的简单实例(精简浓缩版)

    jquery 无限极下拉菜单的简单实例(精简浓缩版) <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>我们 www.jb51.net</title> <script type=&quo

  • 微信小程序下拉菜单效果的实例代码

    //wcss /**DropDownMenu**/ /*总菜单容器*/ .menu { display: block; height: 28px; position: relative; } /*一级菜单*/ .menu dt { font-size: 15px; float: left; /*hack*/ width: 33%; height: 38px; border-right: 1px solid #d2d2d2; border-bottom: 1px solid #d2d2d2; te

  • Layui tree 下拉菜单树的实例代码

    1.效果: 2.html 代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>layui</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" cont

  • iOS使用自带的UIViewController实现qq加号下拉菜单的功能(实例代码)

    创建PopViewControlller 在tableview中创建一个tableview用于显示菜单 //重置控制器的大小 -(CGSize)preferredContentSize{ if (self.popoverPresentationController != nil) { CGSize tempSize ; tempSize.height = self.view.frame.size.height; tempSize.width = 150; CGSize size = [_tabl

  • element-ui下拉菜单组件Dropdown的示例代码

    项目中使用了element ui的页面组件. 在使用dropdown组件的时候,发现了我自己的一些问题,这里记录一下,文末有整理好的我自己用的一个demo.可下载. 基础的使用方法可以参照官方文档: element.eleme.io/#/zh-CN/com… 这里边给出的解释还是很全的,只是,刚刚接触的同学(比如我),有些地方注意不到,有些坑还得踩一踩. 首先上代码: 先是html部分: <div id='app' style="margin:50px;">        

  • Bootstrap下拉菜单效果实例代码分享

    下拉菜单Dropdown不是表单中<select><option value=''></option></select>那种啊,而是导航条中常见的那种. Bootstrap官方网站对下拉菜单Dropdown的解释很少,即使是他们的英文官方网站. 对于如何更改下拉菜单的背景颜色.如果对下拉菜单默认的黑色超级链接进行修改,如何把下拉菜单更改成普通的超级链接而不是文字的样式,官方网站是完全没有具体的解释. 而且,官方网站的超级链接代码杂糅着许多没有用的参数. 笔者

  • ajax三级联动下拉菜单效果

    ajax写三级联动,先写一个文件类吧,以后用的时候直接调用即可: 来找一张表: 实现: 中国地域的三级联动:省.市.区: 图: 说一下思路: (1)当用户选择省份的时候触发事件,把当前的省份的id通过ajax发出请求传递到服务端的程序中 (2)比如取中国地域,中国是0001,那么自带号为0001的便是中国地域: 北京的代号为11,那么子代号为11的便是北京时的市区, 也就是说根据主代号查询子代号: (3)服务端根据客户端的请求,查询数据库,并按照一定的格式返回给客户端 显示页面非常简单,只需要一

随机推荐