MyDropdown

原生js实现简洁的下拉菜单

目前為 2023-04-13 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/463933/1175377/MyDropdown.js

  1. // ==UserScript==
  2. // @name MyDropdown
  3. // @namespace http://https://wish123.cnblogs.com/?MyDropdown
  4. // @version 0.1.1
  5. // @description 原生js实现简洁的下拉菜单
  6. // @author Wilson
  7.  
  8. //构造方法
  9. function MyDropdown(options){
  10. //_count是某实例下的菜单个数
  11. this._count = 0;
  12. this._zIndex = 1000;
  13. this._config = options;
  14. window._MyDropdownPosIndex = (window._MyDropdownPosIndex||this._zIndex)+1;
  15. //实例总个数
  16. window._MyDropdownInsCount = (window._MyDropdownInsCount||0)+1;
  17. //当前第几个实例
  18. this.insCount = window._MyDropdownInsCount;
  19. if(this._config) {
  20. this.config(options);
  21. this.createStyle();
  22. }
  23. //返回对象类型
  24. this.type = function(obj) {
  25. return Object.prototype.toString
  26. .call(obj)
  27. .replace(/^\[object (.+)\]$/, '$1')
  28. .toLowerCase();
  29. }
  30. }
  31. MyDropdown.prototype.createStyle = function() {
  32. if(document.querySelector("#MyDropdownStyle")) {
  33. return;
  34. }
  35. let = style = `
  36. <style>
  37. .my-dropdown-wrapper {
  38. display: none;
  39. position: absolute;
  40. background-color: #f1f1f1;
  41. min-width: 160px;
  42. overflow: auto;
  43. box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  44. z-index: 1;
  45. }
  46.  
  47. .my-dropdown-wrapper a {
  48. color: black;
  49. padding: 12px 16px;
  50. text-decoration: none;
  51. display: block;
  52. }
  53.  
  54. .my-dropdown-wrapper a:hover {background-color: #ddd;}
  55. .my-dropdown-wrapper a.selected {background-color: #ddd;}
  56. .show {display: block;}
  57. .my-dropdown-item-icon svg{width: 24px;height: 24px;float: left;}
  58. .close {
  59. width: 24px;
  60. height: 24px;
  61. display: inline-block;
  62. line-height: 24px;
  63. text-align: center;
  64. font-size: 18px;
  65. float: right;
  66. }
  67. </style>
  68. `
  69. document.body.insertAdjacentHTML("beforeend", style);
  70. }
  71. //设置用户配置
  72. MyDropdown.prototype.config = function(options) {
  73. var _this = this;
  74. //设置用户配置
  75. _this._config = options || _this._config;
  76. if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseover') {
  77. _this._config.toggleEvent = 'mouseenter';
  78. }
  79.  
  80. //绑定事件源按钮点击事件
  81. document.querySelectorAll(_this._config.el||".my-dropdown-btn").forEach(function(item){
  82. item.addEventListener(_this._config.toggleEvent||'click', function(e){
  83. _this.create(this);
  84. _this.show(this);
  85. e.stopPropagation();
  86. return false;
  87. });
  88. });
  89.  
  90. //点击空白,菜单消失
  91. document.addEventListener('click', function(e){
  92. if (!e.target.matches(_this._config.el||'.my-dropdown-btn')) {
  93. _this.hide();
  94. }
  95. });
  96. }
  97. //创建菜单
  98. MyDropdown.prototype.create = function(objBtn, options) {
  99. var _this = this;
  100. options = options || _this._config;
  101. if(options.el) {
  102. _this._config.el = options.el;
  103. }
  104.  
  105. if(objBtn) {
  106. if (!objBtn.dataset.count) {
  107. objBtn.dataset.count = _this._count++;
  108. objBtn.classList.add('my-btn'+_this.insCount+objBtn.dataset.count);
  109. }
  110. }
  111. var count = objBtn.dataset.count || 0;
  112.  
  113. //已存在则返回
  114. if(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`)) {
  115. return;
  116. }
  117. var mouseenterClass = "";
  118. if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
  119. mouseenterClass = " mouseenter";
  120. }
  121. //生成菜单 这里count是某实例下的菜单个数,_this.insCount是实例个数
  122. var menu = `<div id="myDropdownWrapper${_this.insCount}${count}" class="my-dropdown-wrapper${mouseenterClass}" style="overflow:auto;${options.maxWidth?'max-width:'+options.maxWidth+';':''}${options.maxHeight?'max-height:'+options.maxHeight+';':''}" data-count="${count}">`;
  123. if(options.items) {
  124. for(var i in options.items){
  125. var item = options.items[i];
  126. //处理icon选项
  127. var iconHtml = '';
  128. if(item.icon){
  129. if(_this.type(item.icon) === 'object'){
  130. iconHtml = item.icon.html ? item.icon.html : '';
  131. } else {
  132. iconHtml = item.icon ? item.icon : '';
  133. }
  134. }
  135.  
  136. //处理op选项
  137. var opHtml = '';
  138. if(item.op){
  139. if(_this.type(item.op) === 'object'){
  140. opHtml = item.op.html ? item.op.html : '';
  141. } else {
  142. opHtml = item.op ? item.op : '';
  143. }
  144. }
  145. //生成菜单项
  146. menu += `<a href="javascript:;" class="my-dropdown-item ${item.selected?'selected':''}" data-index="${i}" data-value="${item.value}"><span class="my-dropdown-item-icon">${iconHtml}</span>${item.name}<span class="my-dropdown-item-op">${opHtml}</span></a>`;
  147. }
  148. }
  149. menu += `</div>`;
  150. //追加到body中
  151. document.body.insertAdjacentHTML("beforeend", menu);
  152.  
  153. //绑定菜单事件
  154. if(_this._config.toggleEvent && _this._config.toggleEvent.toLowerCase() === 'mouseenter') {
  155. //绑定菜单列表
  156. document.querySelector("#myDropdownWrapper" + _this.insCount + count).addEventListener("mouseleave", function(e){
  157. _this.hide(this);
  158. });
  159. }
  160.  
  161. //绑定菜单item点击事件
  162. document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item").forEach(function(item){
  163. item.addEventListener("click", function(e){
  164. if(typeof this.dataset.index !== 'undefined' && options.items[this.dataset.index]) {
  165. options.items[this.dataset.index].fn(e);
  166. }
  167. });
  168. });
  169.  
  170. //绑定菜单项icon按钮点击事件
  171. document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-icon").forEach(function(item){
  172. //if(!item.innerHTML) return;
  173. item.addEventListener("click", function(e){
  174. if(typeof this.parentElement.dataset.index !== 'undefined' && options.items[this.parentElement.dataset.index] &&
  175. options.items[this.parentElement.dataset.index].icon && options.items[this.parentElement.dataset.index].icon.fn) {
  176. var _this_item = this;
  177. (function(e){
  178. options.items[_this_item.parentElement.dataset.index].icon.fn(e);
  179. e.stopPropagation();
  180. return false;
  181. })(e);
  182. }
  183. });
  184. });
  185.  
  186. //绑定菜单项操作按钮点击事件
  187. document.querySelectorAll("#myDropdownWrapper" + _this.insCount + count + " .my-dropdown-item-op").forEach(function(item){
  188. //if(!item.innerHTML) return;
  189. item.addEventListener("click", function(e){
  190. if(typeof this.parentElement.dataset.index !== 'undefined' && options.items[this.parentElement.dataset.index] &&
  191. options.items[this.parentElement.dataset.index].op && options.items[this.parentElement.dataset.index].op.fn) {
  192. var _this_item = this;
  193. (function(e){
  194. options.items[_this_item.parentElement.dataset.index].op.fn(e);
  195. e.stopPropagation();
  196. return false;
  197. })(e);
  198. }
  199. });
  200. });
  201.  
  202. if(options.created) options.created(document.querySelector(`#myDropdownWrapper${_this.insCount}${count}`));
  203. };
  204. //菜单显示
  205. MyDropdown.prototype.show = function(objBtn, callback) {
  206. var count = objBtn.dataset.count || 0;
  207. var myDropdownWrapper = document.getElementById("myDropdownWrapper"+this.insCount+count);
  208. if(objBtn){
  209. myDropdownWrapper.style.top = (objBtn.offsetTop + objBtn.offsetHeight) + "px";
  210. myDropdownWrapper.style.left = objBtn.offsetLeft + "px";
  211. }
  212. if (myDropdownWrapper.classList.contains('show')) {
  213. if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
  214. return;
  215. }
  216. //隐藏菜单
  217. myDropdownWrapper.classList.remove('show');
  218. if(this._config.hidden) this._config.hidden();
  219. } else {
  220. if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
  221. this.hide();
  222. }
  223. //显示菜单
  224. myDropdownWrapper.style.zIndex = window._MyDropdownPosIndex++;
  225. myDropdownWrapper.classList.add('show');
  226. callback = callback || this._config.shown;
  227. if(callback) callback(myDropdownWrapper);
  228. }
  229. //myDropdownWrapper.classList.toggle("show");
  230. }
  231. //菜单隐藏
  232. MyDropdown.prototype.hide = function(objMenu, callback) {
  233. if(objMenu) {
  234. //单个菜单隐藏
  235. if (objMenu.classList.contains('show')) {
  236. objMenu.classList.remove('show');
  237. callback = callback || this._config.hidden;
  238. if(callback) callback(objMenu);
  239. }
  240. } else {
  241. var mouseenterClass = '';
  242. if(this._config.toggleEvent && this._config.toggleEvent.toLowerCase() === 'mouseenter'){
  243. mouseenterClass = ".mouseenter";
  244. }
  245. //所有菜单隐藏
  246. var dropdowns = document.querySelectorAll(".my-dropdown-wrapper" + mouseenterClass);
  247. for (var i = 0; i < dropdowns.length; i++) {
  248. var openDropdown = dropdowns[i];
  249. if (openDropdown.classList.contains('show')) {
  250. openDropdown.classList.remove('show');
  251. callback = callback || this._config.hidden;
  252. if(callback) callback(openDropdown);
  253. }
  254. }
  255. }
  256. };
  257.  
  258.  
  259. //使用示例
  260. //测试1
  261. // var clicked = function(e) {
  262. // console.log("clicked", e.target.dataset.value)
  263. // }
  264. // new MyDropdown({
  265. // el: ".my-dropdown-btn",
  266. // maxWidth: '200px',
  267. // maxHeight: '400px',
  268. // //支持click mouseenter dblclick等,默认click
  269. // toggleEvent: 'mouseenter',
  270. // items: [
  271. // {
  272. // name: 'Home',
  273. // value: 'home',
  274. // icon: '',
  275. // fn: clicked
  276. // },
  277. // {
  278. // name: 'About',
  279. // value: 'about',
  280. // icon: '',
  281. // selected: false,
  282. // fn: clicked
  283. // },
  284. // {
  285. // name: 'Contact',
  286. // value: 'contact',
  287. // icon: '',
  288. // fn: clicked,
  289. // //icon也支持对象传值,同样具有html和fn属性
  290. // op: {
  291. // html: `<span class="close">&times;</span>`,
  292. // fn: function(e) {
  293. // console.log('op clicked');
  294. // }
  295. // }
  296. // }
  297. // ],
  298. // created: function(menu) {
  299. // console.log('After created callback');
  300. // },
  301. // shown: function(menu) {
  302. // console.log('After shown callback');
  303. // },
  304. // hidden: function(menu) {
  305. // console.log('After hidden callback');
  306. // }
  307. // });
  308.  
  309. // //测试2
  310. // var clicked2 = function(e) {
  311. // console.log("clicked2", e.target.dataset.value)
  312. // }
  313. // new MyDropdown({
  314. // el: ".my-dropdown-btn2",
  315. // items: [
  316. // {
  317. // name: 'Home2',
  318. // value: 'home2',
  319. // icon: '',
  320. // fn: clicked2
  321. // },
  322. // {
  323. // name: 'About2',
  324. // value: 'about2',
  325. // icon: '',
  326. // selected: false,
  327. // fn: clicked2
  328. // },
  329. // {
  330. // name: 'Contact2',
  331. // value: 'contact2',
  332. // icon: '',
  333. // fn: clicked2
  334. // }
  335. // ],
  336. // created: function(menu) {
  337. // console.log('After created callback');
  338. // },
  339. // shown: function(menu) {
  340. // console.log('After shown callback');
  341. // },
  342. // hidden: function(menu) {
  343. // console.log('After hidden callback');
  344. // }
  345. // });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址