el-menu实现横向溢出截取的示例代码

目录
  • 思考
  • 代码部分
  • 总结

antd的menu组件,会在subMenu超出的情况下对超出的subMenu进行截取。 但是element的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-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横向溢出截取内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue+el-menu实现菜单栏无限多层级分类

    本文实例为大家分享了vue+el-menu实现菜单栏无限多层级分类的具体代码,供大家参考,具体内容如下 思路:数据格式须为数组内部多层嵌套模式,利用递归渲染菜单栏数据实现菜单多层级分类. 1.模拟菜单数据,引入封装组件 <template>   <div class="container">     <el-container>       <el-header>Header</el-header>       <el-

  • element 中 el-menu 组件的无限极循环思路代码详解

    实现思路主要组件嵌套(组件自己调用自己) 下面是组件所需要的数据 { "code": 1, "data": { "menuVoList": [ { "childList": [ { "childList": [], "menu": { "createBy": "0-1", "createTime": 1587610158, &q

  • vue2+el-menu实现路由跳转及当前项的设置方法实例

    Vue.js 是什么 Vue.js (读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合.另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动. 好了,下面通过本文给大家介绍vue2+el-menu实现路由跳转及当前项的设置方法,具体内容如下所示: <span style="

  • 使用element-ui的el-menu导航选中后刷新页面保持当前选中状态

    具体代码如下所示: <el-menu :default-active='$route.path' :router='true' :unique-opened='true' :default-openeds="defaultOpeneds" background-color="#bd1e22" text-color="#fff" active-text-color="#ffd04b"> //router当导航激活时允

  • el-menu实现横向溢出截取的示例代码

    目录 思考 代码部分 总结 antd的menu组件,会在subMenu超出的情况下对超出的subMenu进行截取. 但是element的menu组件不会对溢出进行截取 于是我想对element的menu再次进行封装,让它能够支持宽度溢出截取. 思考 查看了antd的源码,还是比较复杂的,他会对每一个subMenu进行一份拷贝,然后隐藏在对应subMenu的后边,然后依赖于resize-observer-polyfill对menu和subMenu进行监听,然后计算超出的subMenu下标.代码量还

  • js使用html2canvas实现屏幕截取的示例代码

    整理文档,搜刮出一个js使用html2canvas实现屏幕截取的示例代码,稍微整理精简一下做下分享. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <

  • C#获取HTML文本的第一张图片与截取内容摘要示例代码

    获取第一张图片 要我们获得到的数据是一段HTML文本.也许这段文本里面有许多图片.需要截取一张作为标题图片.也就是做为主图.这时就可以用到下面这个方法获取到第一张图片. 示例代码 #region 获取第一张图片 /// <summary> /// 获取HTML文本的图片地址 /// </summary> /// <param name="content"></param> /// <returns></returns&g

  • vue 2.0 购物车小球抛物线的示例代码

    本文介绍了vue 2.0 购物车小球抛物线的示例代码,分享给大家,具体如下: 备注:此项目模仿 饿了吗.我用的是最新的Vue, 视频上的一些写法已经被废弃了. 布局代码 <div class="ball-container"> <transition name="drop" v-for="ball in balls" @before-enter="beforeDrop" @enter="droppi

  • Java实现简单的五子棋游戏示例代码

    目录 项目结构 核心代码 ArrComparator.java类 ChessMap.java类 ChessPanel.java类 效果图展示 项目结构 这个是在网上找的资源,出处记不得了,记录一下.程序的总体结构,很简单的: 核心代码 代码如下: ArrComparator.java类 import java.util.Comparator; /** * 排序 Comparator */ class ArrComparator implements Comparator<Object> { i

  • 基于Vue3实现数字华容道游戏的示例代码

    目录 前言 环境 思路 实现 GameCnt GameTool GamePass GameTip Menu 最后 前言 恰逢春之四月,天气忽热忽凉,遇游戏大赛,以笨拙之技,书一篇小文. 游戏规则:存在n*n的格子,需要将它们按数字顺序或图片顺序一一还原即可. 环境 主要环境: vue3 version:3.2.4 vite version:2.5.0 vue-router version:4.0.14 注:这个游戏的路由使用的是自动路由插件 主要插件: windicss version:3.5.

  • C语言实现三子棋游戏的示例代码

    目录 1. 前言 2. 准备工作 3. 使用二维数组存储下棋的数据 4. 初始化棋盘为全空格 5. 打印棋盘 6. 玩家下棋 7. 电脑下棋 8. 判断输赢 9. 效果展示 10. 完整代码 game.h game.c test.c 1. 前言 大家好,我是努力学习游泳的鱼,今天我们会用C语言实现三子棋.所谓三子棋,就是三行三列的棋盘,玩家可以和电脑下棋,率先连成三个的获胜.话不多说,我们开始吧. 2. 准备工作 我们可以在一个项目中创建三个文件,分别是: test.c,测试游戏的逻辑. gam

  • 用vue的双向绑定简单实现一个todo-list的示例代码

    前言 最近在学习vue框架的基本原理,看了一些技术博客以及一些对vue源码的简单实现,对数据代理.数据劫持.模板解析.变异数组方法.双向绑定有了更深的理解.于是乎,尝试着去实践自己学到的知识,用vue的一些基本原理实现一个简单的todo-list,完成对深度复杂对象的双向绑定以及对数组的监听,加深了对vue基本原理的印象. github地址:todo-list 学习链接 前排感谢以下文章,对我理解vue的基本原理有很大的帮助! 剖析vue实现原理,自己动手实现mvvm by DMQ 对vue早期

  • ionic2屏幕适配实现适配手机、平板等设备的示例代码

    本文介绍了ionic2屏幕适配实现适配手机.平板等设备的示例代码,分享给大家,具体如下: 推荐使用的编辑器是:VS code  (Visual Studio Code)=>只负责编辑文档,不编译. 而WebStorm 有检查编译等,在ionic1开发的时候,还很方便用浏览器随时点击按键浏览效果,但是开发ionic2之后,ionic2有自动检查编译,会照成webstorm卡顿,无法编辑. 一.首先增加一个一面作为测试 我使用的工程是sidemenu 在项目目录下执行如下命令: ionic g pa

  • Android控件ListView用法(读取联系人示例代码)

    示例代码: 这是一个读取联系人的代码: 复制代码 代码如下: package com.ui.domain; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.database.Cursor; import android.database.DataSetObserver; import android.graphics.Color; import andro

随机推荐