我们利用 vue.js 的自定义指令能力, 来实现一个自定义下拉菜单功能. 描述如下:
点击按钮, 弹出下拉菜单.
点击下拉菜单之外的区域, 关闭下拉菜单.
1 基础版
html:
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Title
- </title>
- <link rel="stylesheet" type="text/CSS" href="style.css">
- </head>
- <body>
- <div id="app" v-cloak>
- <div class="main" v-outside-click="close">
- <button @click="isShow=!isShow">
- 点击
- </button>
- <div class="dropDown" v-show="isShow">
- <p>
- 零售新物种: 药店和便利店合体之后
- </p>
- </div>
- </div>
- </div>
- <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js">
- </script>
- <script src="index.js">
- </script>
- </body>
- </HTML>
我们为按钮绑定了 isShow 变量, 当点击按钮时, 显示 class="dropDown" 的 div 元素.
JS:
- Vue.directive('outside-click', {
- bind: function (el, binding, vnode) {
- // 定义点击函数
- function clickHandler(e) {
- if (el.contains(e.target)) {// 如果点击区域在所在指令元素内部, 则直接返回
- return false;
- }
- if (binding.expression) {// 如果定义了表达式, 则执行表达式中的函数
- binding.value(e);
- }
- }
- el.vueOutsideClick = clickHandler;
- document.addEventListener('click', clickHandler);// 绑定到 document 的点击事件
- },
- unbind: function (el, binding, vnode) {
- document.removeEventListener('click', el.vueOutsideClick);// 解绑
- delete el.vueOutsideClick;// 销毁
- }
- });
- var App = new Vue({
- el: '#app',
- data: {
- isShow: false
- },
- methods: {
- close: function () {
- this.isShow = false;
- }
- }
- });
bind 中:
首先在定义了点击函数, 内部逻辑为: 如果点击区域在所在指令元素内部, 则直接返回; 如果定义了表达式, 则执行表达式中的函数 (示例中是 close).
这里用到了 contains 函数, A.contains(B) 是判断元素 A 是否包含了元素 B.
接着在 el 中定义了一个变量, 用于存放刚才定义的点击函数. bind() 与 unbind() 通过 el 变量进行参数传递.
然后绑定到 document 的点击事件.
unbind 中:
解绑在 bind 中绑定的点击事件.
销毁该变量.
CSS:
- [v-cloak] {
- display: none;
- }
- .main {
- width: 125px;
- }
- button {
- display: block;
- width: 100%;
- color: #ffffff;
- background-color: #99CC66;
- border: 0;
- padding: 6px;
- text-align: center;
- font-size: 12px;
- border-radius: 4px;
- cursor: pointer;
- position: relative;
- outline: none;
- }
- button:active {
- top: 1px;
- left: 1px;
- }
- .dropDown {
- width: 100%;
- height: 150px;
- margin: 5px 0;
- font-size: 12px;
- background-color: #ffffff;
- border-radius: 4px;
- box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
- }
- .dropDown p {
- display: inline-block;
- padding: 6px;
- }
效果:
2 ESC 关闭
现在让我们做个优化, 即在按下键盘的 ESC 键时, 也能关闭下拉菜单.
JS:
- bind: function (el, binding, vnode) {
- function clickHandler(e) {
- if (el.contains(e.target) && e.keyCode !== 27) {
- return false;
- }
- ...
- }
- ...
- document.addEventListener('keyup', clickHandler, false);// 绑定键盘事件
- },
- unbind: function (el, binding, vnode) {
- ...
- document.removeEventListener('keyup', el.vueOutsideClick);// 解绑
- ...
- }
在 bind 函数中, 强化了判断, 如果点击区域在所在指令元素内部并且没有按下 ESC 键时, 才直接返回. 即按下 ESC 键时, 会执行后续操作 (执行表达式中的函数).
在 unbind 函数中, 也解绑了 keyup 事件.
效果:
3 ESC 为可选项
我们可以把 ESC 作为可选项, 而这可以通过修饰符来实现.
JS:
- bind: function (el, binding, vnode) {
- // 定义点击函数
- function clickHandler(e) {
- // 是否开启开关
- var escSwitch = (binding.modifiers && binding.modifiers.esc);
- if (el.contains(e.target)) {// 如果点击区域在所在指令元素内部时
- if (escSwitch && e.keyCode === 27) {// 带有了 esc 修饰符, 则让程序往下执行
- } else {// 直接返回
- return false;
- }
- }
- if (binding.expression) {// 如果定义了表达式, 则执行表达式中的函数
- binding.value(e);
- }
- }
- ...
- }
我们通过 binding.modifiers 来判断自定义指令是否设置了 esc 修饰符, 然后以此为基础, 来编写后续逻辑.
HTML:
- <div id="app" v-cloak>
- <div class="main" v-outside-click.esc="close">
- ...
- </div>
- </div>
本文示例代码 https://jsfiddle.net/deniro/m4qw5rnf/
来源: http://www.jianshu.com/p/5364f1b3832a