手寫實現(xiàn)vue2下拉菜單dropdown組件實例
概述
一般后臺項目結(jié)合分頁組件用到這個dropdown組件,用來做顯示分頁條數(shù),其他用到這個組件的地方倒不是很多,其實現(xiàn)思路和select組件有那么些相似,現(xiàn)記錄下這個組件的實現(xiàn)。
最終效果(動圖沒顯示出來,請稍定會兒,可以先看后面)

實現(xiàn)原理
這個組件和select組件記起相似,可以參考我之前的文章【手寫vue2select下拉組件】,要做這個組件,需要注意以下幾點:
組件分為兩部分:
- 供我們點擊的文字,按鈕,超鏈接等等(當成插槽供用戶提供)
- 下拉菜單項(支持邊框,禁用等)
使用該組件應(yīng)當提供的事件應(yīng)該是點擊item項,然后將對應(yīng)的item的對應(yīng)value暴露出來,供用戶使用。
組件菜單項的顯示隱藏需要過渡動畫。
默認菜單項方向向下,當下方可視區(qū)的高度不足以容納下拉菜單的高度的時候,自動讓菜單方向向上。
具體實現(xiàn)
目錄結(jié)構(gòu)

emitter.js
這個在之前的組件實現(xiàn)過程中介紹過這個文件,主要是為了解決跨多層級父子組件之前數(shù)據(jù)通信的,本質(zhì)上實現(xiàn)原理為發(fā)布訂閱模式。
/**
* @Description 由于涉及到跨組件之間通信,因此我們只有自己實現(xiàn)發(fā)布訂閱的模式,來實現(xiàn)組件之間通信,靈感主要來源于element-ui組件庫源碼中跨層級父子組件通信方案,本質(zhì)上也是發(fā)布訂閱和$emit和$on
* @param { String } componentName 組件名
* @param { String } eventName 事件名
* @param { argument } params 參數(shù)
**/
// 廣播通知事件
function _broadcast(componentName, eventName, params) {
// 遍歷當前組件的子組件
this.$children.forEach(function (child) {
// 取出componentName,組件options上面可以自己配置
var name = child.$options.componentName;
// 如果找到了需要通知的組件名,觸發(fā)組件上面的$eimit方法,觸發(fā)自定義事件
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
// 沒找到,遞歸往下找
_broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
const emiiter = {
methods: {
// 派發(fā)事件(通知父組件)
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 循環(huán)往上層父組件,知道知道組件名和需要觸發(fā)的組件名相同即可,然后觸發(fā)對應(yīng)組件的事件
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: {
// 觸發(fā)顯示方式
trigger: {
type: String,
default: "click",
},
// 下來菜單的出現(xiàn)位置(上方,下方)
placement: {
type: String,
default: "bottom",
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ["bottom", "top"].includes(value);
},
},
},
data() {
return {
//控制菜單是否顯示
showMenu: false,
};
},
mounted() {
//初始化自定義事件
this.initEvent();
},
methods: {
// 初始化
initEvent() {
//訂閱當item點擊的時候,發(fā)布on-click事件,告知外部
this.$on("item-click", (params) => {
this.$emit("on-click", params);
this.showMenu = false;
});
//空白點擊要隱藏菜單,需要執(zhí)行的函數(shù)需要綁定this指向
this.handleEmptyDomElementClickBindThis =
this.handleEmptyDomElementClick.bind(this);
window.addEventListener("click", this.handleEmptyDomElementClickBindThis);
},
// 處理空白區(qū)域點擊,隱藏菜單列表
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鉤子函數(shù)的方式比較好處理些 -->
<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) {
//獲取文檔可視區(qū)高度
const htmlClientHeight = document.documentElement.clientHeight;
//菜單列表相對于父元素的top偏移量
const offsetTop = el.offsetTop;
const scrollHeight = el.scrollHeight;
//獲取當前元素和可視區(qū)的一些長度(top,left,bottom等)
const { bottom } = el.getBoundingClientRect();
// 說明底部高度不夠顯示菜單了,這時候我們需要調(diào)整菜單朝上面顯示
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;
//根據(jù)官網(wǎng)事例,必須在enter和leave里面調(diào)用done函數(shù),不然過渡動畫不生效(切記)
done();
},
//dom元素離開
leave: function (el, done) {
el.style.transform = "scaleY(0)";
el.style.opacity = 0;
clearTimeout(this.timer);
this.timer = setTimeout(() => {
//根據(jù)官網(wǎng)事例,必須在enter和leave里面調(diào)用done函數(shù),不然過渡動畫不生效(切記)
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
主要暴露給用戶使用的組件,菜單列表項組件,組件內(nèi)容很簡單,主要就是展示item數(shù)據(jù)和綁定點擊事件。
<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組件派發(fā)到外部的自定義事件
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>
總結(jié)
類似組件庫中的這種經(jīng)常出現(xiàn)跨多層級組件通信,需要特殊處理,一般emitter.js文件里面的封裝我們在開發(fā)中一般是用不到的,我們寫組件經(jīng)常也就父子組件之間,很少會跨祖孫級別,但是在組件庫中,這種關(guān)系就很多,因此需要單獨利用發(fā)布訂閱來處理,這種模式用到實際項目里面也是很管用的。
以上就是手寫實現(xiàn)vue2下拉菜單dropdown組件實例的詳細內(nèi)容,更多關(guān)于vue 下拉菜單dropdown的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue-calendar-component 封裝多日期選擇組件的實例代碼
這篇文章主要介紹了vue-calendar-component 封裝多日期選擇組件,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
vue 實現(xiàn)v-for循環(huán)回來的數(shù)據(jù)動態(tài)綁定id
今天小編就為大家分享一篇vue 實現(xiàn)v-for循環(huán)回來的數(shù)據(jù)動態(tài)綁定id,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11

