概述

之前写vue的时候,对于下拉框,我是通过在组件内设置标记来控制是否弹出的,但是这样有一个问题,就是点击组件外部的时候,怎么也控制不了下拉框的关闭,用户体验非常差。

当时想到的解决方法是:给根实例创建一个标记来控制,然后一级一级的把这个标记传进来。但是这样每次配置都要改根组件,非常不灵活

最近看museUI库,发现它的下拉框Select实现的非常灵活,点击组件外也能控制下拉框关闭,于是想探究一番,借此机会也深入学习一下vue。

museUI源码

首先去看Select的源码:

directives: [{
name: 'click-outside',
value: (e) => {
if (this.open && this.$refs.popover.$el.contains(e.target)) return;
this.blur();
}
}],

可以看到,有个click-outsidepopover,然后它是通过用自定义指令directives实现的。然后去museUI搜popover,果然这是一个弹出组件,并且能够在组件外部控制弹窗关闭。于是开始看popover的源码

close (reason) {
if (!this.open) return;
this.$emit('update:open', false);
this.$emit('close', reason);
},
clickOutSide (e) {
if (this.trigger && this.trigger.contains(e.target)) return;
this.close('clickOutSide');
},

可以看到,它也是通过click-outside来实现的,click-outside字面意思是点击外面,应该就是这个了。然后看click-outside的源码

name: 'click-outside',
bind (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
if (binding.expression) {
vnode.context[el[clickoutsideContext].methodName](e);
} else {
el[clickoutsideContext].bindingFn(e);
}
};
el[clickoutsideContext] = {
documentHandler,
methodName: binding.expression,
bindingFn: binding.value
};
setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
},

原来它是通过自定义指令,在组件创建的时候,给document绑定一个全局click事件,当点击document的时候,通过判断点击节点来控制弹窗关闭的。这差不多就是事件代理

所以总结一下,要实现组件外部控制组件弹窗的关闭,主要利用directives,bind,document就行了。

自己实现

既然知道原理就有点跃跃欲试了,通过查阅官方文档得知,directives可以用于局部组件,这样就变成了局部指令。于是写代码如下:

<template>
<div class="pop-over">
<a @click="toggleOpen" class="pop-button" href="javascript: void(0);">
{{ 按钮1 }}
</a>
<ul v-clickoutside="close" v-show="open" class="pop-list">
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
</ul>
</div>
</template> <script>
export default {
name: 'PopOver',
data() {
return {
open: false
}
},
methods: {
toggleOpen: function() {
this.open = !this.open;
},
close: function(e) {
if(this.$el.contains(e.target)) return;
this.open = false;
}
},
directives: {
clickoutside: {
bind: function (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
binding.value(e);
}; setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
}
}
}
}
</script>

注意,在我们close方法里面,我们通过判断点击节点是否被组件包含,如果包含的话,不执行关闭行为。

但是上面的组件不通用,正好官方文档学习了slot,于是用slot改写如下:

<template>
<div class="pop-over">
<a @click="toggleOpen" class="pop-button" href="javascript: void(0);">
{{ buttonText }}
</a>
<ul v-clickoutside="close" v-show="open" class="pop-list">
<slot></slot>
</ul>
</div>
</template> <script>
export default {
name: 'PopOver',
props: ['buttonText'],
data() {
return {
open: false
}
},
methods: {
toggleOpen: function() {
this.open = !this.open;
},
close: function(e) {
if(this.$el.contains(e.target)) return;
this.open = false;
}
},
directives: {
clickoutside: {
bind: function (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
binding.value(e);
}; setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
}
}
}
}
</script> <style scoped>
.pop-over {
position: relative;
width: 100%;
height: 100%;
}
.pop-button {
position: relative;
width: 100%;
height: 100%;
text-decoration:none;
color: inherit;
}
.pop-list {
position: absolute;
left: 0;
top: 0;
}
.pop-list li {
width: 100%;
height: 100%;
padding: 8px 3px;
list-style:none;
}
</style>

利用props自定义按钮文字,slot自定义弹窗文字,这样一个简易的Popover组件就完成了。

我学到了什么

  1. directives自定义指定,事件代理,slot练手一番,感觉很爽。
  2. 在看源码的过程中,也看到了render方法的使用,以及museUI的组件化思想
  3. 对于组件外控制组件的行为有了新的思路。

vue实现一个简易Popover组件的更多相关文章

  1. 过年了,基于Vue做一个消息通知组件

    前言 今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!项目一览 效果很简单,就是这种的小卡片似的效果. 我们先开始写UI页面,可自定义消息 ...

  2. vue封装一个弹框组件

    这是一个提示框和对话框,例:   这是一个组件 eject.vue <template> <div class='kz-cont' v-show='showstate'> &l ...

  3. Angular2-编写一个简易的组件

    Angular2组件可以这么理解:编写一个类,然后在类的上面用组件装饰器装饰一下,这个类就成组件了. 所以编写组件分两步:1)编写类:2)编写装饰器 1)编写类: export class Simpl ...

  4. vue实现一个会员卡的组件(可以动态传入图片(分出的一个组件)、背景、文字、卡号等)

    自己在写这个组件的时候主要遇到的问题就是在动态传入背景图片或者背景色的时候没能立马顺利写出来,不过现在实现了这个简单组件就和大家分享一下 <template> <div class= ...

  5. vue小案例--简易评论区

    一.小案例(评论区) 1.流程 (1)分析静态页面.(vue项目创建参考https://www.cnblogs.com/l-y-h/p/11241503.html)(2)拆分静态页面,变成一个个组件. ...

  6. vue + socket.io实现一个简易聊天室

    vue + vuex + elementUi + socket.io实现一个简易的在线聊天室,提高自己在对vue系列在项目中应用的深度.因为学会一个库或者框架容易,但要结合项目使用一个库或框架就不是那 ...

  7. 发布自己第一个npm 组件包(基于Vue的文字跑马灯组件)

    一.前言 总结下最近工作上在移动端实现的一个跑马灯效果,最终效果如下: 印象中好像HTML标签的'marquee'的直接可以实现这个效果,不过 HTML标准中已经废弃了'marquee'标签 既然HT ...

  8. 一个vue的全局提示框组件

    <template> <!-- 全局提示框 --> <div v-show="visible" class="dialog-tips dia ...

  9. Vue 实现一个分页组件

    实现分页组件要分三个部分 样式,逻辑,和引用 首先新建一个vue文件用来承载组件内容 第一步:构建样式 <template> <nav> <ul class=" ...

随机推荐

  1. angular中文文档的滚动条样式

    个人感觉angular中文文档的滚动条样式非常棒,于是乎就扒了下来 https://www.angular.cn/ body::-webkit-scrollbar { /* 定义了滚动条整体的样式 * ...

  2. 1.为什么使用spring boot

    最近2年spring cloud微服务比较流行,Spring Cloud基于SpringBoot,为微服务体系开发中的架构问题提供了一整套的解决方案, 本文总结一下为什么要使用Spring boot, ...

  3. Element transfer 两边数据(左右)的显示问题?

    本仙今天遇到这个穿梭框的问题 这个是我前几天刚换的(原来用的是iview的,换成了element ) 别问我为什么,用过iview的都知道 转入正题 问题:从后台获取的数据全部都显示在了我的左边框中 ...

  4. RDMA RC UC UD

    RC:面向连接的可靠服务 UC:面向连接的不可靠服务 UD:面向数据报的不可靠服务 RD:面向非连接(类似UDP)的可靠服务 面向连接 vs 面向数据报 相同点:两者的通信均包括双方QP对的参与 不同 ...

  5. HDU 3586.Information Disturbing 树形dp 叶子和根不联通的最小代价

    Information Disturbing Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/ ...

  6. python基础之Day22

    1.组合 什么是? 一个类的对象具备某一个属性,该属性值属于另一个类的对象,这样就可以引用 为何用: 解决类与类之间代码冗余问题 如何用? 2.菱形继承 单继承:一个个往父类上查找 菱形:一个子类继承 ...

  7. Android自动化之Monkey测试(二)

    本文主要从以下方面进行分享. 一.查看应用包名二.Monkey启动三.Monkey停止四.Monkey命令五.日志分析 一.查看应用包名 大多数时候,我们都是对特定的应用进行monkey测试,因此需要 ...

  8. 团队-爬虫豆瓣top250项目-模块测试过程

    模块测试: 项目托管平台地址:https://github.com/gengwenhao/GetTop250.git 模块测试:"获取250排名的全部电影信息"功能,测试方法:手动 ...

  9. 第4周小组作业:WordCount优化

     Github项目地址:https://github.com/chaseMengdi/wcPro stage1:代码编写+单元测试 PSP表格 PSP2.1 PSP阶段 预估耗时(分钟) 实际耗时(分 ...

  10. JavaScript基础整理

    在HTML页面中嵌入JavaScript代码的三种方式 1.内嵌 写在<head>标签或<body>标签中 <script> function displayTim ...