原生js实现简洁好用的下拉菜单
自己开发的js下拉菜单,简洁,方便自定义和扩充,主要用于Chrome浏览器,没有严格测试,可能有不足之处。
Demo预览
jsrun.net/3cdKp/embedded/all/light
使用示例:
var clicked = function(e) {
console.log("clicked", e.target.dataset.value)
}
new MyDropdown({
el: ".my-dropdown-btn",
maxWidth: '200px',
maxHeight: '400px',
//支持click mouseenter dblclick等,默认click
toggleEvent: 'mouseenter',
items: [
{
name: 'Home',
value: 'home',
icon: '',
fn: clicked
},
{
name: 'About',
value: 'about',
icon: '',
selected: false,
fn: clicked
},
{
name: 'Contact',
value: 'contact',
icon: '',
fn: clicked,
//icon也支持对象传值,同样具有html和fn属性
op: {
html: `<span class="close">×</span>`,
fn: function(e) {
console.log('op clicked');
}
}
}
],
created: function(menu) {
console.log('After created callback1');
},
shown: function(menu) {
console.log('After shown callback1');
},
hidden: function(menu) {
console.log('After hidden callback1');
}
});
源码:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.my-dropdown-wrapper {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
.my-dropdown-wrapper .my-dropdown-item:hover{
background-color: #ddd;
}
.my-dropdown-wrapper .my-dropdown-item.selected {background-color: #ddd;}
.my-dropdown-wrapper a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
padding-right: 6px;
overflow: hidden;
text-overflow: ellipsis;
}
.my-dropdown-wrapper a:hover {background-color: #ddd;}
.my-dropdown-wrapper a.selected {background-color: #ddd;}
.show {display: block;}
.my-dropdown-item-icon svg{width: 24px;height: 24px;float: left;}
.my-dropdown-wrapper .close {
width: 24px;
height: 24px;
display: inline-block;
line-height: 24px;
text-align: center;
font-size: 18px;
float: right;
position: relative;
top: 10px;
right: 6px;
cursor: pointer;
}
.my-dropdown-wrapper .close:hover{
opacity: 0.6;
}
</style>
</head>
<body>
<div>
<button class="my-dropdown-btn">Mouseenter Dropdown1.1</button>
<button class="my-dropdown-btn">Mouseenter Dropdown1.2</button>
<button class="my-dropdown-btn2">Click Dropdown2.1</button>
<button class="my-dropdown-btn2">Click Dropdown2.2</button>
<button id="test3">Click Dropdown3</button>
</div>
<script>
//构造方法
function MyDropdown(options){
//_count是某实例下的菜单个数
this._count = 0;
this._zIndex = 1000;
this._config = options;
window._MyDropdownPosIndex = (window._MyDropdownPosIndex||this._zIndex)+1;
//实例总个数
window._MyDropdownInsCount = (window._MyDropdownInsCount||0)+1;
//当前第几个实例
this.insCount = window._MyDropdownInsCount;
if(this._config) {
this.config(options);
}
}
//返回对象类型
MyDropdown.prototype.type = function(obj) {
return Object.prototype.toString
.call(obj)
.replace(/^\[object (.+)\]$/, '$1')
.toLowerCase();
}
//设置用户配置
MyDropdown.prototype.config = function(options) {
var _this = this;
//设置用户配置
_this._config = options || _this._config;
if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseover') {
_this._config.toggleEvent = 'mouseenter';
}
if(_this.type(_this._config.el) !== 'string') {
//手动绑定
//初始化事件按钮
_this._config.byManual = true;
const btnObj = _this._config.el.target || _this._config.el;
let event = null;
if(_this._config.el.target) {
event = _this._config.el;
}
if(_this._config.event) {
event = _this._config.event;
}
let id = btnObj.getAttribute("id");
if(!id) {
id = "myDropdownBtn" + _this.insCount;
btnObj.setAttribute("id", id);
}
const count = btnObj.dataset.count;
if(!count) btnObj.dataset.count = 1;
btnObj.classList.add('my-btn'+_this.insCount+btnObj.dataset.count);
if(!btnObj.classList.contains('my-btn-by-manual')) btnObj.classList.add('my-btn-by-manual');
_this._config.el = "#" + id;
_this.create(btnObj);
_this.show(btnObj);
if(event) event.stopPropagation();
} else {
//自动绑定
//绑定事件源按钮点击事件
document.querySelectorAll(_this._config.el||".my-dropdown-btn").forEach(function(item){
item.addEventListener(_this._config.toggleEvent||'click', function(e){
_this.create(this);
_this.show(this);
e.stopPropagation();
return false;
});
});
}
}
//创建菜单
MyDropdown.prototype.create = function(objBtn, options) {
var _this = this;
options = options || _this._config;
if(options.el) {
_this._config.el = options.el;
}
if(objBtn) {
if (!objBtn.dataset.count) {
objBtn.dataset.count = _this._count++;
objBtn.classList.add('my-btn'+_this.insCount+objBtn.dataset.count);
}
}
var count = objBtn.dataset.count || 0;
//已存在则返回
if(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`)) {
return;
}
//点击空白,菜单消失(每次显示后,重新绑定一次)
if(!window._MyDropdownEventListener) {
window._MyDropdownEventListener = 1;
document.addEventListener('click', (e) => {
if (!e.target.matches(this._config.el||'.my-dropdown-btn')) {
this.hide();
}
});
}
var mouseenterClass = "";
if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
mouseenterClass = " mouseenter";
}
var byManualClass = "";
if(objBtn.classList.contains("my-btn-by-manual")) {
byManualClass = " my-dropdown-by-manual"
}
//生成菜单 这里count是某实例下的菜单个数,_this.insCount是实例个数
var menu = `<div id="myDropdownWrapper${_this.insCount}${count}" class="my-dropdown-wrapper${mouseenterClass}${byManualClass}" style="overflow:auto;${options.maxWidth?'max-width:'+options.maxWidth+';':''}${options.maxHeight?'max-height:'+options.maxHeight+';':''}" data-count="${count}">`;
if(options.items) {
for(var i in options.items){
var item = options.items[i];
//处理icon选项
var iconHtml = '';
if(item.icon){
if(_this.type(item.icon) === 'object'){
iconHtml = item.icon.html ? item.icon.html : '';
} else {
iconHtml = item.icon ? item.icon : '';
}
}
//处理op选项
var opHtml = '';
if(item.op){
if(_this.type(item.op) === 'object'){
opHtml = item.op.html ? item.op.html : '';
} else {
opHtml = item.op ? item.op : '';
}
}
//生成菜单项
menu += `<div class="my-dropdown-item${item.selected?' selected':''}" data-index="${i}" data-value="${item.value}"><span class="my-dropdown-item-op">${opHtml}</span><a href="##" class="${item.selected?'selected':''}" data-index="${i}" data-value="${item.value}"><span class="my-dropdown-item-icon">${iconHtml}</span>${item.name}</a></div>`;
}
}
menu += `</div>`;
//追加到body中
document.body.insertAdjacentHTML("beforeend", menu);
//记录hidden回调
window._MyDropdownConfig = window._MyDropdownConfig || {};
window._MyDropdownConfig[`myDropdownWrapper${_this.insCount}${count}`] = _this._config || {};
//绑定菜单事件
if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
//绑定菜单列表
document.querySelector("#myDropdownWrapper" + _this.insCount + count).addEventListener("mouseleave", function(e){
_this.hide(this);
});
}
//绑定菜单item点击事件
document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item").forEach(function(item){
item.addEventListener("click", function(e){
if(typeof this.dataset.index !== 'undefined' && options.items[this.dataset.index]) {
options.items[this.dataset.index].fn(e);
}
});
});
//绑定菜单项icon按钮点击事件
document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-icon").forEach(function(item){
//if(!item.innerHTML) return;
item.addEventListener("click", function(e){
if(typeof this.parentElement.dataset.index !== 'undefined' && options.items[this.parentElement.dataset.index] &&
options.items[this.parentElement.dataset.index].icon && options.items[this.parentElement.dataset.index].icon.fn) {
var _this_item = this;
(function(e){
options.items[_this_item.parentElement.dataset.index].icon.fn(e);
e.stopPropagation();
return false;
})(e);
}
});
});
//绑定菜单项操作按钮点击事件
document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-op").forEach(function(item){
//if(!item.innerHTML) return;
item.addEventListener("click", function(e){
if(typeof this.nextElementSibling.dataset.index !== 'undefined' && options.items[this.nextElementSibling.dataset.index] &&
options.items[this.nextElementSibling.dataset.index].op && options.items[this.nextElementSibling.dataset.index].op.fn) {
var _this_item = this;
(function(e){
options.items[_this_item.nextElementSibling.dataset.index].op.fn(e);
e.stopPropagation();
return false;
})(e);
}
});
});
if(options.created) options.created(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`));
};
//菜单显示
MyDropdown.prototype.show = function(objBtn, callback) {
var count = objBtn.dataset.count || 0;
var myDropdownWrapper = document.getElementById("myDropdownWrapper"+this.insCount+count);
if(objBtn){
myDropdownWrapper.style.top = (objBtn.offsetTop + objBtn.offsetHeight) + "px";
myDropdownWrapper.style.left = objBtn.offsetLeft + "px";
}
if (myDropdownWrapper.classList.contains('show')) {
if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
return;
}
//隐藏菜单
myDropdownWrapper.classList.remove('show');
if(this._config.hidden) this._config.hidden();
} else {
if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
this.hide();
}
//显示菜单
myDropdownWrapper.style.zIndex = window._MyDropdownPosIndex++;
myDropdownWrapper.classList.add('show');
callback = callback || this._config.shown;
if(callback) callback(myDropdownWrapper);
}
//myDropdownWrapper.classList.toggle("show");
}
//菜单隐藏
MyDropdown.prototype.hide = function(objMenu, callback) {
objMenu = typeof objMenu === 'string' ? document.querySelector(objMenu) : objMenu;
if(objMenu) {
//单个菜单隐藏
var id = objMenu.getAttribute("id");
var config = window._MyDropdownConfig[id] || {};
if(config.byManual && objMenu.classList.contains("my-dropdown-by-manual")){
let idNum = objMenu.getAttribute("id").match(/\d+/);
idNum = idNum ? (idNum[0]||0) : 0;
let myBtn = document.querySelector(".my-btn"+idNum);
if(myBtn) myBtn.classList.remove("my-btn"+idNum);
objMenu.remove();
callback = callback || config.hidden;
if(callback) callback(objMenu);
} else {
if (objMenu.classList.contains('show')) {
objMenu.classList.remove('show');
callback = callback || config.hidden;
if(callback) callback(objMenu);
}
}
} else {
//所有菜单隐藏
var dropdowns = document.querySelectorAll(".my-dropdown-wrapper");
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
var id = openDropdown.getAttribute("id");
var config = window._MyDropdownConfig[id] || {};
if(config.byManual && openDropdown.classList.contains("my-dropdown-by-manual")){
let idNum = openDropdown.getAttribute("id").match(/\d+/);
idNum = idNum ? (idNum[0]||0) : 0;
let myBtn = document.querySelector(".my-btn"+idNum);
if(myBtn) myBtn.classList.remove("my-btn"+idNum);
openDropdown.remove();
if(window._MyDropdownConfig[id] && window._MyDropdownConfig[id].hidden){
callback = callback || config.hidden;
if(callback) callback(openDropdown);
}
delete window._MyDropdownConfig[id];
} else {
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
if(window._MyDropdownConfig[id] && window._MyDropdownConfig[id].hidden){
callback = callback || config.hidden;
if(callback) callback(openDropdown);
}
}
}
}
}
}
//使用示例
//测试1
var clicked = function(e) {
console.log("clicked", e.target.dataset.value)
}
new MyDropdown({
el: ".my-dropdown-btn",
maxWidth: '200px',
maxHeight: '400px',
//支持click mouseenter dblclick等,默认click
toggleEvent: 'mouseenter',
items: [
{
name: 'Home',
value: 'home',
icon: '',
fn: clicked
},
{
name: 'About',
value: 'about',
icon: '',
selected: false,
fn: clicked
},
{
name: 'Contact',
value: 'contact',
icon: '',
fn: clicked,
//icon也支持对象传值,同样具有html和fn属性
op: {
html: `<span class="close">×</span>`,
fn: function(e) {
console.log('op clicked');
}
}
}
],
created: function(menu) {
console.log('After created callback1');
},
shown: function(menu) {
console.log('After shown callback1');
},
hidden: function(menu) {
console.log('After hidden callback1');
}
});
//测试2
var clicked2 = function(e) {
console.log("clicked2", e.target.dataset.value)
}
new MyDropdown({
el: ".my-dropdown-btn2",
items: [
{
name: 'Home2',
value: 'home2',
icon: '',
fn: clicked2
},
{
name: 'About2',
value: 'about2',
icon: '',
selected: false,
fn: clicked2
},
{
name: 'Contact2',
value: 'contact2',
icon: '',
fn: clicked2
}
],
created: function(menu) {
console.log('After created callback2');
},
shown: function(menu) {
console.log('After shown callback2');
},
hidden: function(menu) {
console.log('After hidden callback2');
}
});
//测试3
document.querySelector("#test3").addEventListener('click', function(e){
new MyDropdown({
el: this,
maxWidth: '200px',
maxHeight: '400px',
items: [
{
name: 'Home3',
value: 'home3',
fn: (e) => {
console.log("clicked", e.target.dataset.value)
}
},
{
name: 'About3',
value: 'about3',
selected: false,
fn: (e) => {
console.log("clicked", e.target.dataset.value)
}
},
{
name: 'Contact3',
value: 'contact3',
selected: false,
fn: (e) => {
console.log("clicked", e.target.dataset.value)
},
op: {
html: `<span class="close">×</span>`,
fn: (e) => {
console.log('op clicked');
}
}
}
],
created: function(menu) {
console.log('After created callback3');
},
shown: function(menu) {
console.log('After shown callback3');
},
hidden: function(menu) {
console.log('After hidden callback3');
}
});
e.stopPropagation();
return false;
});
</script>
</body>
</html>
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: