目录
1. 问题 + 需求
问题:菜单项文字过多时,多于文字被遮挡,看不全
需求:文字过多时,鼠标悬浮当前菜单项 -> 文字进行滚动
2. 解决思路
- 添加鼠标悬浮事件
- 计算、判断
文字内容所在的 span 标签宽度
是否大于菜单项宽度 - 菜单项内边距 - 10px
(-10px 是因为有空隙) - 如果 span 标签宽度大,说明文字溢出,给 span 添加滚动(
定时器 + 设置 left
)- 【注意】设置 left 时要给 span 添加定位
- 添加鼠标移出事件:清除定时器、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. 值的注意的点
- 获取dom所有样式
window.getComputedStyle(DOM元素, null)
- 获取dom指定样式值
window.getComputedStyle(DOM元素, null).getPropertyValue('样式属性名')
获取到的样式是带单位的 - 取绝对值
Math.abs(-10)
6. 以上代码出现的问题
- 【问题】
- 项目刚打开是登录页,加载了这个文件但是并没有 menu 菜单项,所以点击到有菜单项的页面时,不会出现滚动(因为 mounted 中代码只有刚开始加载页面时运行了一次)
- 切换页面没有触发侦听的
$route
- 切换页面时,页面中有 el-menu-item 但是取到的 menuDomArr 是空的
- 【解决】
- 将 mounted 中代码封装为一个方法,放到 methods 中,然后侦听 $route 如果发生改变,就执行该段代码。
- 侦听
$route
时,加上deep
和immedate
- 给
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 文字一起滚动,并且越来越快
视频展示:
内网多行一起滚动且越来越快
解决:不滚动了,直接展示全部文字
- 在 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> ...
data
中添加变量overFlowText
来存放鼠标悬浮过的当前页面的文字过多的菜单项文字data() { return { ... overFlowText: [] // 溢出文字 }; },
- 侦听
$route
中路由每切换一次overFlowText
置空watch: { $route: { handler(val) { console.log('route val----', val) this.overFlowText = [] // 置空 this.menuAddScroll() }, deep: true, immediate: true } }
- 鼠标悬浮 -> 计算如果当前文字过多导致溢出 -> 将当前文字 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);
}