<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
antd的menu元件,會在subMenu超出的情況下對超出的subMenu進行擷取。 但是element的menu元件不會對溢位進行擷取
於是我想對element的menu再次進行封裝,讓它能夠支援寬度溢位擷取。
檢視了antd的原始碼,還是比較複雜的,他會對每一個subMenu進行一份拷貝,然後隱藏在對應subMenu的後邊,然後依賴於resize-observer-polyfill對menu和subMenu進行監聽,然後計算超出的subMenu下標。程式碼量還是比較多的,看到最後有點迷糊。
後來我進行了一些思考,需求大概如下
<template> <el-menu class="sweet-menu" v-bind="$attrs" v-on="$listeners" > <!-- 傳入的menu --> <slot /> <!-- ...按鈕 --> <sub-menu v-if="ellipsis" :list="overflowedElements" /> </el-menu> </template>
首先確定template部分 僅僅是需要將傳入的引數透傳給el-menu,然後通過預設插槽的形式接收傳入的子元素。最後渲染出溢位部分的展示開關。
//subMenu元件 export default { props: { list: {}, }, render(h) { return h('template', [ h('el-submenu', { attrs: { key: 'overflow-menu', index: 'overflow-menu', 'popper-append-to-body': true, }, class: { 'overflow-btn': true, }, }, [ h('span', { slot: 'title' }, '...'), ...this.list, ]), ]); }, };
subMenu元件的主要作用是渲染出傳入的list,list其實就是一段從$slots.default中拿到的VNode列表。
import ResizeObserver from 'resize-observer-polyfill'; import subMenu from './subMenu.vue'; import { setStyle, getWidth, cloneElement } from './utils'; //偏差部分 const FLOAT_PRECISION_ADJUST = 0.5; export default { name: 'SweetMenu', components: { subMenu, }, data() { return { // 所有menu寬度總和 originalTotalWidth: 0, resizeObserver: null, // 最後一個可展示menu的下標 lastVisibleIndex: undefined, // 溢位的subMenus overflowedItems: [], overflowedElements: [], // 所有menu寬度集合 menuItemSizes: [], lastChild: undefined, // 所有menu集合 ulChildrenNodes: [], // 原始slots.defaule備份 originSlots: [], }; }, computed: { ellipsis() { return this.$attrs?.mode === 'horizontal'; }, }, mounted() { if (!this.ellipsis) return; // 備份slots.default this.originSlots = this.$slots.default.map((vnode) => cloneElement(vnode)); // 拿到...按鈕 // eslint-disable-next-line prefer-destructuring this.lastChild = [].slice.call(this.$el.children, -1)[0]; // 拿到所有li this.ulChildrenNodes = [].slice.call(this.$el.children, 0, -1); // 儲存每個menu的寬度 this.menuItemSizes = [].slice .call(this.ulChildrenNodes) .map((c) => getWidth(c)); // 計算menu寬度總和 this.originalTotalWidth = this.menuItemSizes.reduce( (acc, cur) => acc + cur, 0, ); // 註冊監聽事件 this.$nextTick(() => { this.setChildrenWidthAndResize(); if (this.$attrs.mode === 'horizontal') { const menuUl = this.$el; if (!menuUl) return; this.resizeObserver = new ResizeObserver((entries) => { entries.forEach(this.setChildrenWidthAndResize); }); this.resizeObserver.observe(menuUl); } }); }, methods: { setChildrenWidthAndResize() { if (this.$attrs.mode !== 'horizontal' || !this.$el) return; const { lastChild, ulChildrenNodes } = this; // ...按鈕的寬度 const overflowedIndicatorWidth = getWidth(lastChild); if (!ulChildrenNodes || ulChildrenNodes.length === 0) { return; } // 拿到所有slots.default this.$slots.default = this.originSlots.map((vnode) => cloneElement(vnode)); // 解決內容區撐開ul寬度問題 ulChildrenNodes.forEach((c) => { setStyle(c, 'display', 'none'); }); // 獲取el-menu寬度 const width = getWidth(this.$el); // 可展示menu寬度總和 let currentSumWidth = 0; // 最後一個可展示menu的下標 let lastVisibleIndex; // 如果寬度溢位 if (this.originalTotalWidth > width + FLOAT_PRECISION_ADJUST) { lastVisibleIndex = -1; this.menuItemSizes.forEach((liWidth) => { currentSumWidth += liWidth; if (currentSumWidth + overflowedIndicatorWidth <= width) { lastVisibleIndex += 1; } }); } this.lastVisibleIndex = lastVisibleIndex; // 過濾menu相關dom this.overflowedItems = [].slice .call(ulChildrenNodes) .filter((c, index) => index > lastVisibleIndex); this.overflowedElements = this.$slots.default.filter( (c, index) => index > lastVisibleIndex, ); // 展示所有li ulChildrenNodes.forEach((c) => { setStyle(c, 'display', 'inline-block'); }); // 對溢位li隱藏 this.overflowedItems.forEach((c) => { setStyle(c, 'display', 'none'); }); // 判斷是否需要顯示... setStyle( this.lastChild, 'display', lastVisibleIndex === undefined ? 'none' : 'inline-block', ); // 去除隱藏的menu 解決hover時 被隱藏的menu彈窗同時出現問題 this.$slots.default = this.$slots.default.filter((vnode, index) => index <= lastVisibleIndex); }, }, };
在js部分,主要是對subMenu寬度進行了判斷,通過menuItemSizes
儲存所有subMenu的寬度,然後拿到this.$el
也就是容器ul的寬度。通過遞增的方式,判斷是否溢位,然後記錄lastVisibleIndex
。這裡需要注意的就是記得要加上最後一個subMenu的寬度
。
然後是一些css樣式的處理
.sweet-menu { overflow: hidden; position: relative; white-space: nowrap; width: 100%; ::v-deep & > .el-menu-item { position: relative; } ::v-deep .overflow-btn { .el-submenu__icon-arrow { display: none; } } ::v-deep .sweet-icon { margin-right: 0.5rem; } }
這裡我們只是對horizontal
模式進行了處理,vertical
模式還是相容的,所以只需要像使用el-menu
的方式進行使用 就可以了
//utils.js部分 import classNames from 'classnames'; const camelizeRE = /-(w)/g; const camelize = (str) => str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); export function isEmptyElement(c) { return !(c.tag || (c.text && c.text.trim() !== '')); } const filterEmpty = (children = []) => children.filter((c) => !isEmptyElement(c)); // eslint-disable-next-line default-param-last const parseStyleText = (cssText = '', camel) => { const res = {}; const listDelimiter = /;(?![^(]*))/g; const propertyDelimiter = /:(.+)/; cssText.split(listDelimiter).forEach((item) => { if (item) { const tmp = item.split(propertyDelimiter); if (tmp.length > 1) { const k = camel ? camelize(tmp[0].trim()) : tmp[0].trim(); res[k] = tmp[1].trim(); } } }); return res; }; function cloneVNodes(vnodes, deep) { const len = vnodes.length; const res = new Array(len); // eslint-disable-next-line no-plusplus for (let i = 0; i < len; i++) { // eslint-disable-next-line no-use-before-define res[i] = cloneVNode(vnodes[i], deep); } return res; } const cloneVNode = (vnode, deep) => { const { componentOptions } = vnode; const { data } = vnode; let listeners = {}; if (componentOptions && componentOptions.listeners) { listeners = { ...componentOptions.listeners }; } let on = {}; if (data && data.on) { on = { ...data.on }; } const cloned = new vnode.constructor( vnode.tag, data ? { ...data, on } : data, vnode.children, vnode.text, vnode.elm, vnode.context, componentOptions ? { ...componentOptions, listeners } : componentOptions, vnode.asyncFactory, ); cloned.ns = vnode.ns; cloned.isStatic = vnode.isStatic; cloned.key = vnode.key; cloned.isComment = vnode.isComment; cloned.fnContext = vnode.fnContext; cloned.fnOptions = vnode.fnOptions; cloned.fnScopeId = vnode.fnScopeId; cloned.isCloned = true; if (deep) { if (vnode.children) { cloned.children = cloneVNodes(vnode.children, true); } if (componentOptions && componentOptions.children) { componentOptions.children = cloneVNodes(componentOptions.children, true); } } return cloned; }; // eslint-disable-next-line default-param-last const cloneElement = (n, nodeProps = {}, deep) => { let ele = n; if (Array.isArray(n)) { // eslint-disable-next-line prefer-destructuring ele = filterEmpty(n)[0]; } if (!ele) { return null; } const node = cloneVNode(ele, deep); // // 函數式元件不支援clone https://github.com/vueComponent/ant-design-vue/pull/1947 // warning( // !(node.fnOptions && node.fnOptions.functional), // ); const { props = {}, key, on = {}, nativeOn = {}, children, directives = [], } = nodeProps; const data = node.data || {}; let cls = {}; let style = {}; const { attrs = {}, ref, domProps = {}, style: tempStyle = {}, class: tempCls = {}, scopedSlots = {}, } = nodeProps; if (typeof data.style === 'string') { style = parseStyleText(data.style); } else { style = { ...data.style, ...style }; } if (typeof tempStyle === 'string') { style = { ...style, ...parseStyleText(style) }; } else { style = { ...style, ...tempStyle }; } if (typeof data.class === 'string' && data.class.trim() !== '') { data.class.split(' ').forEach((c) => { cls[c.trim()] = true; }); } else if (Array.isArray(data.class)) { classNames(data.class) .split(' ') .forEach((c) => { cls[c.trim()] = true; }); } else { cls = { ...data.class, ...cls }; } if (typeof tempCls === 'string' && tempCls.trim() !== '') { tempCls.split(' ').forEach((c) => { cls[c.trim()] = true; }); } else { cls = { ...cls, ...tempCls }; } node.data = { ...data, style, attrs: { ...data.attrs, ...attrs }, class: cls, domProps: { ...data.domProps, ...domProps }, scopedSlots: { ...data.scopedSlots, ...scopedSlots }, directives: [...(data.directives || []), ...directives], }; if (node.componentOptions) { node.componentOptions.propsData = node.componentOptions.propsData || {}; node.componentOptions.listeners = node.componentOptions.listeners || {}; node.componentOptions.propsData = { ...node.componentOptions.propsData, ...props }; node.componentOptions.listeners = { ...node.componentOptions.listeners, ...on }; if (children) { node.componentOptions.children = children; } } else { if (children) { node.children = children; } node.data.on = { ...(node.data.on || {}), ...on }; } node.data.on = { ...(node.data.on || {}), ...nativeOn }; if (key !== undefined) { node.key = key; node.data.key = key; } if (typeof ref === 'string') { node.data.ref = ref; } return node; }; const getWidth = (elem) => { let width = elem && typeof elem.getBoundingClientRect === 'function' && elem.getBoundingClientRect().width; if (width) { width = +width.toFixed(6); } return width || 0; }; const setStyle = (elem, styleProperty, value) => { if (elem && typeof elem.style === 'object') { elem.style[styleProperty] = value; } }; export { cloneElement, setStyle, getWidth, };
到此這篇關於el-menu實現橫向溢位擷取的文章就介紹到這了,更多相關el-menu橫向溢位擷取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45