概述

之前写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. boost中Function和Lambda的使用

    :first-child { margin-top: 0px; } .markdown-preview:not([data-use-github-style]) h1, .markdown-previ ...

  2. Linux_软件安装_jdk_tomcat_Mysql

    双击要安装的文件(或右键传输) 1. JDK的安装1.1 准备工作:安装依赖的环境 yum install glibc.i686 yum –y install libaio.so.1 libgcc_s ...

  3. 使用iconfont图标

    iconfont.cn基本使用 登录iconfont.cn网站,直接使用github账号即可登录 输入关键字搜索需要的图标,然后在需要的图标上点击'添加入库' 点击网站右上角的购物车图标,查看当前已入 ...

  4. matplotlib 初次编译无法运行

    终端 解决方案:vim ~/.matplotlib/matplotlibrc 输入backend: TkAgg 保存

  5. 26. pt-summary

    pt-summary # Percona Toolkit System Summary Report ###################### Date | 2018-11-23 10:48:51 ...

  6. iptables命令提取总结,包含扩展模块<取自朱双印博客>

    以下内容只是一些命令相关的,以朱双印博客中的iptables的教程提取出来的.纯粹只是命令的总结,如果需要看理论的知识,建议去看朱老师的博客,目前还没有看到写得比这个好的了. <http://w ...

  7. Java中的Integer和int

    Java中的Integer是引用类型,而int是基本类型.Integer是int的包装器类型. java中的基本类型有布尔类型boolean;字符类型char;整数类型byte,int,long,sh ...

  8. js跳转到页面指定元素

    var scrollDistance = $("#设置了的overflow元素").scrollTop() + $('#' + 当前屏幕元素).offset().top; $(&q ...

  9. Mybatis批量更新比较

    https://blog.csdn.net/lu1024188315/article/details/78758943

  10. makefile与动态链接库案例分析——动态库链接动态库

    http://blog.csdn.net/huqinwei987/article/details/50517780 背景:效率考虑,要重用把服务器主备机方案,以库Libmdpha(高可用)的形式加进主 ...