广告
返回顶部
首页 > 资讯 > 精选 >el-menu如何实现横向溢出截取
  • 392
分享到

el-menu如何实现横向溢出截取

2023-06-30 17:06:34 392人浏览 八月长安
摘要

这篇文章主要介绍了el-menu如何实现横向溢出截取的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇el-menu如何实现横向溢出截取文章都会有所收获,下面我们一起来看看吧。antd的menu组件,会在subMe

这篇文章主要介绍了el-menu如何实现横向溢出截取的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇el-menu如何实现横向溢出截取文章都会有所收获,下面我们一起来看看吧。

el-menu如何实现横向溢出截取

antd的menu组件,会在subMenu超出的情况下对超出的subMenu进行截取。 但是element的menu组件不会对溢出进行截取

el-menu如何实现横向溢出截取

于是我想对element的menu再次进行封装,让它能够支持宽度溢出截取。

思考

查看了antd的源码,还是比较复杂的,他会对每一个subMenu进行一份拷贝,然后隐藏在对应subMenu的后边,然后依赖于resize-observer-polyfill对menu和subMenu进行监听,然后计算超出的subMenu下标。代码量还是比较多的,看到最后有点迷糊。
后来我进行了一些思考,需求大概如下

  • 通过resize-observer-polyfill对页面变化进行监听

  • 计算宽度是否溢出,以及subMenu下标lastVisbileIndex是多少

  • 渲染出溢出的subMenu集合

  • 底层还是使用el-menu

代码部分

<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-lastconst 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-lastconst 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如何实现横向溢出截取”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网精选频道。

--结束END--

本文标题: el-menu如何实现横向溢出截取

本文链接: https://www.lsjlt.com/news/330624.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • el-menu如何实现横向溢出截取
    这篇文章主要介绍了el-menu如何实现横向溢出截取的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇el-menu如何实现横向溢出截取文章都会有所收获,下面我们一起来看看吧。antd的menu组件,会在subMe...
    99+
    2023-06-30
  • el-menu实现横向溢出截取的示例代码
    目录思考代码部分总结 antd的menu组件,会在subMenu超出的情况下对超出的subMenu进行截取。 但是element的menu组件不会对溢出进行截取 于是我想对elem...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作