菜单项文字过多时,多余文字被遮挡 - 【解决】文字过多时让其滚动 -【最终目的】用户能看到菜单项中所有文字

1. 问题 + 需求

问题:菜单项文字过多时,多于文字被遮挡,看不全
需求:文字过多时,鼠标悬浮当前菜单项 -> 文字进行滚动

在这里插入图片描述

2. 解决思路

  1. 添加鼠标悬浮事件
  2. 计算、判断 文字内容所在的 span 标签宽度 是否大于 菜单项宽度 - 菜单项内边距 - 10px (-10px 是因为有空隙)
  3. 如果 span 标签宽度大,说明文字溢出,给 span 添加滚动(定时器 + 设置 left
    • 【注意】设置 left 时要给 span 添加定位
  4. 添加鼠标移出事件:清除定时器、left 和步长 step 值进行重置

页面标签/样式图示
在这里插入图片描述
在这里插入图片描述

3. 解决代码

<template>
  <section class="AppAside">
    <div class="collapse" @click="handleCollapse" >
      <i v-if="isCollapse" class="el-icon-arrow-right" ></i>
      <span v-else>
        <i :class="['icon', 'iconfont', localIcon]"></i>
        <span style="margin-left: 10px">{
   
   { activeMenuTitle }}</span>
        <i class="el-icon-arrow-left"></i>
      </span>
    </div>
    <el-menu
      :default-active="activeSubIndex"
      class="el-menu-vertical"
      :background-color="el_menu_style.backgroundColor"
      :text-color="el_menu_style.textColor"
      :collapse="isCollapse"
    >
      <template v-for="(item, index) in activeMenus">
        <el-submenu v-if="item.childrenNode && item.childrenNode.length !== 0 && item.data.cdlj === null" :index="item.data.mkdm + index.toString()" :key="index" >
          <p slot="title">
            <i :class="'icon iconfont ' + item.data.tpdz" color="#fff" ></i>
            <font v-if="!isCollapse">{
   
   { item.data.mkmc }}</font>
          </p>
          <!-- 【主要代码】 - start -->
          <el-menu-item
            class="elMenuItem"
            v-for="(obj, index1) in item.childrenNode"
            :index="obj.data.mkdm + index1.toString()"
            :key="index1"
            @click.native="handleSelect(obj, index1)"
          >
            <i :class="'icon iconfont ' + obj.data.tpdz" color="#fff" ></i>
            <!-- 【主要代码】 -->
            <span slot="title">{
   
   { obj.data.mkmc }}</span>
            <!-- <span v-if="obj.data.mkmc && obj.data.mkmc.length < 10" slot="title">{
    
    { obj.data.mkmc }}</span>
            <marquee v-else direction="left" width="100%" align="middle" scrollamount="5">{
    
    { obj.data.mkmc }}</marquee> -->
          </el-menu-item>
          <!-- 【主要代码】- end -->
        </el-submenu>
        <el-menu-item
          v-else
          :index="item.data.mkdm + index.toString()"
          :key="index"
          @click.native="handleSelect(item, index)"
        >
          <i
            :class="'icon iconfont ' + item.data.tpdz"
            color="#fff"
          ></i>
          <span slot="title">{
   
   { item.data.mkmc }}</span>
        </el-menu-item>
      </template>
    </el-menu>
  </section>
</template>

<script>
export default {
      
      
  data() {
      
      
    return {
      
      
      activeSubIndex: "0",
      localIcon: "",
      timer: null, // 定时器
      step: 1 // 滚动 步长
    };
  },
  mounted() {
      
      
    let menuDomArr = document.getElementsByClassName('elMenuItem')
    // Array.from() 将伪数组转为真正数组,不然添加不上事件
    Array.from(menuDomArr).forEach((item, index) => {
      
      
      /**** 添加鼠标悬浮事件 ****/
      item.addEventListener('mouseenter', (e)=>{
      
      
        // 获取元素宽度(包括内边距)
        const itemWidth = item.offsetWidth // item 有 padding-left 40px
        // 获取元素样式
        let itemStyle = window.getComputedStyle(item, null)
        // 获取元素左内边距(将带单位的内边距处理为不带单位的)
        let itemPaddingL = parseFloat(itemStyle.getPropertyValue('padding-left'))
        // 获取内部 span 宽度(文字宽度)
        const spanWidth = item.lastChild.offsetWidth
        if(itemWidth - itemPaddingL - 10 < spanWidth) {
      
      
          // console.log('"溢出"----', "溢出")
          this.animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth)
        }
        // console.log('e----', e)
        console.log('-------------item.innerText----', item.innerText)
      })
      /**** 添加鼠标移开事件 ****/
      item.addEventListener('mouseleave', () => {
      
      
        item.lastChild.style.left = `0px`
        this.step = 1
        clearInterval(this.timer)
        this.timer = null
      })
    })
  },
  methods() {
      
      
  	animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth) {
      
      
      this.timer = setInterval(() => {
      
      
        // 向左自动滚动到头之后文字从右侧继续出来
        if(Math.abs(this.step) > spanWidth + itemPaddingL) this.step = itemWidth - itemPaddingL
        this.step--
        menuDomArr[index].lastChild.style.left = `${ 
        this.step}px`
      }, 50); 
    }
  },
}
</script>

<style lang="scss" scoped>
.elMenuItem span {
      
      
  position: relative; // 【注意】一定要给span添加定位,不然设置left无效
}
</style>

4. 效果展示

左侧菜单文字过多时滚动 - 视频展示

5. 值的注意的点

  1. 获取dom所有样式 window.getComputedStyle(DOM元素, null)
  2. 获取dom指定样式值 window.getComputedStyle(DOM元素, null).getPropertyValue('样式属性名') 获取到的样式是带单位的
  3. 取绝对值 Math.abs(-10)

6. 以上代码出现的问题

  • 【问题】
    1. 项目刚打开是登录页,加载了这个文件但是并没有 menu 菜单项,所以点击到有菜单项的页面时,不会出现滚动(因为 mounted 中代码只有刚开始加载页面时运行了一次)
    2. 切换页面没有触发侦听的 $route
    3. 切换页面时,页面中有 el-menu-item 但是取到的 menuDomArr 是空的
  • 【解决】
    1. 将 mounted 中代码封装为一个方法,放到 methods 中,然后侦听 $route 如果发生改变,就执行该段代码。
    2. 侦听 $route 时,加上 deepimmedate
    3. menuAddScrollHandler 方法中代码添加 this.$nextTick(()=>{})
<template>
  <section class="AppAside">
    <div class="collapse" @click="handleCollapse" >
      <i v-if="isCollapse" class="el-icon-arrow-right" ></i>
      <span v-else>
        <i :class="['icon', 'iconfont', localIcon]"></i>
        <span style="margin-left: 10px">{
   
   { activeMenuTitle }}</span>
        <i class="el-icon-arrow-left"></i>
      </span>
    </div>
    <el-menu
      :default-active="activeSubIndex"
      class="el-menu-vertical"
      :background-color="el_menu_style.backgroundColor"
      :text-color="el_menu_style.textColor"
      :collapse="isCollapse"
    >
      <template v-for="(item, index) in activeMenus">
        <el-submenu v-if="item.childrenNode && item.childrenNode.length !== 0 && item.data.cdlj === null" :index="item.data.mkdm + index.toString()" :key="index" >
          <p slot="title">
            <i :class="'icon iconfont ' + item.data.tpdz" color="#fff" ></i>
            <font v-if="!isCollapse">{
   
   { item.data.mkmc }}</font>
          </p>
          <!-- 【主要代码】 - start -->
          <el-menu-item
            class="elMenuItem"
            v-for="(obj, index1) in item.childrenNode"
            :index="obj.data.mkdm + index1.toString()"
            :key="index1"
            @click.native="handleSelect(obj, index1)"
          >
            <i :class="'icon iconfont ' + obj.data.tpdz" color="#fff" ></i>
            <!-- 【主要代码】 -->
            <span slot="title">{
   
   { obj.data.mkmc }}</span>
            <!-- <span v-if="obj.data.mkmc && obj.data.mkmc.length < 10" slot="title">{
    
    { obj.data.mkmc }}</span>
            <marquee v-else direction="left" width="100%" align="middle" scrollamount="5">{
    
    { obj.data.mkmc }}</marquee> -->
          </el-menu-item>
          <!-- 【主要代码】- end -->
        </el-submenu>
        <el-menu-item
          v-else
          :index="item.data.mkdm + index.toString()"
          :key="index"
          @click.native="handleSelect(item, index)"
        >
          <i
            :class="'icon iconfont ' + item.data.tpdz"
            color="#fff"
          ></i>
          <span slot="title">{
   
   { item.data.mkmc }}</span>
        </el-menu-item>
      </template>
    </el-menu>
  </section>
</template>

<script>
export default {
      
      
  data() {
      
      
    return {
      
      
      activeSubIndex: "0",
      localIcon: "",
      timer: null, // 定时器
      step: 1 // 滚动 步长
    };
  },
  watch: {
      
      
    $route: {
      
      
      handler(val) {
      
      
        console.log('route  val----', val)
        this.menuAddScrollHandler()
      },
      deep: true,
      immediate: true
    }
  },
  mounted() {
      
      },
  methods() {
      
      
  	menuAddScrollHandler(){
      
      
      this.$nextTick(() => {
      
      
        console.log('"滚动 menuAddScrollHandler"----', "滚动")
        let menuDomArr = document.getElementsByClassName('elMenuItem')
        console.log('menuDomArr----', menuDomArr)
        Array.from(menuDomArr).forEach((item, index) => {
      
      
          item.addEventListener('mouseenter', (e) => {
      
      
            // 获取元素宽度(包括内边距)
            const itemWidth = item.offsetWidth // item 有 padding-left 40px
            // console.log('itemWidth----', itemWidth)
            // 获取元素样式
            let itemStyle = window.getComputedStyle(item, null)
            // console.log('style----', itemStyle)
            // 获取元素左内边距(将带单位的内边距处理为不带单位的)
            let itemPaddingL = parseFloat(itemStyle.getPropertyValue('padding-left'))
            // console.log('itemPaddingL----', itemPaddingL)

            // console.log('item.lastChild----', item.lastChild)
            // 获取内部 span 宽度(文字宽度)
            const spanWidth = item.lastChild.offsetWidth
            // console.log('spanWidth----', spanWidth)
            if(itemWidth - itemPaddingL - 10 < spanWidth) {
      
      
              console.log('"溢出"----', "溢出")
              this.animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth)
            }
            // console.log('-------------item.innerText----', item.innerText)
          })
          item.addEventListener('mouseleave', () => {
      
      
            item.lastChild.style.left = `0px`
            this.step = 1
            clearInterval(this.timer)
            this.timer = null
          })
        })
      })
    },
  	animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth) {
      
      
      this.timer = setInterval(() => {
      
      
        // 向左自动滚动到头之后文字从右侧继续出来
        if(Math.abs(this.step) > spanWidth + itemPaddingL) this.step = itemWidth - itemPaddingL
        this.step--
        menuDomArr[index].lastChild.style.left = `${ 
        this.step}px`
      }, 50); 
    }
  },
}
</script>

<style lang="scss" scoped>
.elMenuItem span {
      
      
  position: relative; // 【注意】一定要给span添加定位,不然设置left无效
}
</style>

【内网问题】

鼠标悬浮多行超出文本的 menu 文字一起滚动,并且越来越快
视频展示:

内网多行一起滚动且越来越快

解决:不滚动了,直接展示全部文字

  1. 在 html 代码中添加 el-tooltip
    ...
    <el-menu-item
    class="elMenuItem"
    v-for="(obj, index1) in item.childrenNode"
    :index="obj.data.mkdm + index1.toString()"
    :key="index1"
    @click.native="handleSelect(obj, index1)"
     >
       <i
         :class="'icon iconfont ' + obj.data.tpdz"
         color="#fff"
       ></i>
       <!-- <span slot="title">{
          
          { obj.data.mkmc }}</span> -->
       <template slot="title">  <!-- 【主要代码】 -->
         <el-tooltip
           :disabled="!overFlowText.includes(obj.data.mkmc)"
           :enterable="false"
           effect="dark"
           :content="obj.data.mkmc"
           placement="top-start"
         >
           <span>{
         
         { obj.data.mkmc }}</span>
         </el-tooltip>
       </template>
     </el-menu-item>
     ...
    
  2. data 中添加变量 overFlowText 来存放鼠标悬浮过的当前页面的文字过多的菜单项文字
    data() {
          
          
      return {
          
          
        ...
        overFlowText: [] // 溢出文字
      };
    },
    
  3. 侦听 $route 中路由每切换一次 overFlowText 置空
    watch: {
          
          
      $route: {
          
          
        handler(val) {
          
          
          console.log('route  val----', val)
          this.overFlowText = [] // 置空
          this.menuAddScroll()
        },
        deep: true,
        immediate: true
      }
    }
    
  4. 鼠标悬浮 -> 计算如果当前文字过多导致溢出 -> 将当前文字 push 进overFlowText中
    methods: {
          
          
      menuAddScrollHandler(){
          
          
        ...
        if(itemWidth - itemPaddingL - 10 < spanWidth) {
          
          
          console.log('"溢出"----', "溢出")
          // let text = item.innerText
          // console.log('text----', text)
          if(!this.overFlowText.includes(item.innerText)) this.overFlowText.push(item.innerText)
          console.log('this.overFlowText----', this.overFlowText)
          // this.animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth) // 去掉之前的滚动
        }
        ...
      }
    }
    

页面效果展示

在这里插入图片描述

【内网问题原因】

侦听 $route 时,给 dom 重复添加了多个鼠标移入事件,导致鼠标移入一次就会开始多个计时器,关闭计时器的时候只是关闭了最后一个计时器(timer 的值只记录了最后一个计时器的值,最后一个之前的定时器的值都被覆盖掉了,所以取消不了)

解决

在开启一个新的定时器之前先判断目前有没有开启的定时器,如果有的话就不再开启新的定时器,这样就不会出现开启多个定时器,timer值被覆盖的问题

animation(menuDomArr, index, itemWidth, itemPaddingL, spanWidth) {
    
    
  if(this.timer) return // 【主要代码】
  this.timer = setInterval(() => {
    
    
    // 向左自动滚动到头之后文字从右侧继续出来
    if(Math.abs(this.step) > spanWidth + itemPaddingL) this.step = itemWidth - itemPaddingL
    this.step--
    menuDomArr[index].lastChild.style.left = `${
      
      this.step}px`
  }, 50); 
}

猜你喜欢

转载自blog.csdn.net/m0_53562074/article/details/128204608