这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言
记录分享每一个日常开发项目中的实用小知识,不整那些虚头巴脑的框架理论与原理,之前分享过抽奖功能、签字功能等,有兴趣的可以看看本人以前的分享。 今天要分享的实用小知识是最近项目中遇到的标签相关的功能,我不知道叫啥,姑且称之为【多行标签展开隐藏】功能吧,类似于多行文本展开折叠功能,如果超过最大行数则显示展开隐藏按钮,如果不超过则不显示按钮。多行文本展开与折叠功能在网上有相当多的文章了,也有许多开源的封装组件,而多行标签展开隐藏的文章却比较少,刚好最近我也遇到了这个功能,所以就单独拿出来与大家分享如何实现。
出处
【多行标签展开与隐藏】该功能我们平时可能没注意一般在哪里会有,其实最常见的就是各种APP的搜索页面的历史记录这里,下面是我从拼多多(左)和腾讯学堂小程序(右)截下来的功能样式:
其它APP一般搜索的历史记录这里都有这个小功能,比如京东、支付宝、淘宝、抖音、快手等,可能稍有点儿不一样,有的是按钮样式,有的是只有展开没有收起功能,可能我们用过了很多年平时都没有注意到这个小功能,有想了解的可以去看一看哈。如果有一天你们需要开发一个搜索页面的话产品就很有可能出这样的一个功能,接下来我们就来看看这种功能我们该如何实现。
功能实现
我们先看实现的效果图,然后再分析如何实现,效果图如下:

【样式一】:标签容器和展开隐藏按钮分开(效果图样式一)
标签容器和按钮分开的这种样式功能实现起来的话我个人觉得难度稍微简单一些,下面我们看看如何实现这种分开的功能。
第一种方法:通过与第一个标签左偏移值对比实现
原理:遍历每个标签然后通过与第一个标签左偏移值对比,如果有几个相同偏移值则说明有几个换行
具体实现上代码:
<div class="list-con list-con-1">
<div class="label">人工智能</div>
<div class="label">人工智能与应用</div>
<div class="label">行业分析与市场数据</div>
<div class="label">标签标签标签标签标签标签标签标签</div>
<div class="label">标签</div>
<div class="label">啊啊啊</div>
<div class="label">宝宝贝贝</div>
<div class="label">微信</div>
<div class="label">吧啊啊</div>
<div class="label">哦哦哦哦哦哦哦哦</div>
</div>
<div class="expand expand-1">展开 ∨</div>
<script>
const listCon = document.querySelector('.list-con-1')
const expandBtn = document.querySelector('.expand-1')
console.log(listCon.children)
// HTMLCollection对象 item()、namedItem()方法 length属性
let firstLabelOffsetLeft = 0 // 第一个标签左侧偏移
let line = 1 // 记录行
const len = listCon.children.length
for(let i = 0; i < len; i++) {
const _offsetLeft = listCon.children.item(i).offsetLeft
if (i === 0) {
firstLabelOffsetLeft = _offsetLeft
} else if (firstLabelOffsetLeft === _offsetLeft) {
line++
console.log(line + '行')
}
}
// 如果大于一行则隐藏
if (line > 2) {
expandBtn.style = 'display: show'
} else {
expandBtn.style = 'display: none'
}
expandBtn.addEventListener('click', () => {
const _classList = listCon.classList
if (_classList.contains('list-expand')) {
expandBtn.innerHTML = '展开 ∨'
} else {
expandBtn.innerHTML = '收起 ∧'
}
_classList.toggle('list-expand') // 这个更简洁
})
</script>
解析:HTML
布局就不用多说了,是个前端都知道该怎么搞,如果不知道趁早送外卖去吧,多说无益,把机会留给其他人。其次CSS
应该也是比较简单的,注意的是有个前提需要先规定容器的最大高度,然后使用overflow超出隐藏,这样展开就直接去掉该属性,让标签自己撑开即可。JavaScript
部分我这里没有使用啥框架,因为这块实现就是个简单的Demo所以就用纯原生写比较方便,这里我们先获取容器,然后获取容器的孩子节点(这里我们也可以直接通过className查询出所有标签元素),返回的是一个可遍历的变签对象,然后我们记录第一个标签的offsetLeft左偏移值,接下来遍历所有的标签元素,如果有与第一个标签相同的值则累加,最终line表示有几行,如果超过我们最大行数(demo超出2行隐藏)则显示展开隐藏按钮。
第二种方法:通过计算容器高度对比
原理:通过容器底部与标签top比较,如果有top值大于容器底部bottom则表示超出容器隐藏。
具体上代码:
<script>
const listCon2 = document.querySelector('.list-con-2')
const expandBtn2 = document.querySelector('.expand-2')
const listCon2Height = listCon2.getBoundingClientRect().bottom
const len2 = listCon2.children.length
for(let i = 0; i < len2; i++) {
const _top = listCon2.children.item(i).getBoundingClientRect().top
// 通过top判断如果有标签大于容器bottom则隐藏
if (_top >= listCon2Height) {
expandBtn2.style = 'display: show'
break
} else {
expandBtn2.style = 'display: none'
}
}
expandBtn2.addEventListener('click', () => {
const _classList = listCon2.classList
// console.log(_classList)
if (_classList.contains('list-expand')) {
expandBtn2.innerHTML = '展开 ∨'
} else {
expandBtn2.innerHTML = '收起 ∧'
}
_classList.toggle('list-expand')
})
</script>
解析:HTML
和CSS
同方法一同,不同点在于这里是通过getBoundingClientRect()方法来判断,还是遍历所有标签,不同的是如果有标签的top值大于等于了容器的bottom值,则说明了标签已超出容器,则要显示展开隐藏按钮,展开隐藏还是通过容器overflow属性来实现比较简单。
【样式二】:展开隐藏按钮和标签同级(效果图样式二)
这种样式也是绝大部分APP产品使用的风格,不信你可以打开抖音商城或汽车之家的搜索历史,十个产品九个是这样设计的,不是这样的我倒立洗头。 这种放在同级的就相对稍微难一点,因为要把展开隐藏按钮塞到标签的最后,如果是隐藏的话就要切割标签展示数量,那下面我就带大家看看我是是如何实现的。
方法一:通过遍历高度判断
原理:同样式一的高度判断一样,通过容器底部bottom与标签top比较,如果有top值大于容器顶部bottom则表示超出容器隐藏,不同的是如何计算标签展示的长度。有个前提是按钮和标签的的宽度要做限制,最好是一行能放一个标签和按钮。
具体实现上代码:
<div id="app3">
<div class="list-con list-con-3" :class="{'list-expand': isExpand}">
<div class="label" v-for="item in labelArr.slice(0, labelLength)">{{ item }}</div>
<div class="label expand-btn" v-if="showExpandBtn" @click="changeExpand">{{ !isExpand ? '展开 ▼' : '隐藏 ▲' }}</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, nextTick } = Vue
createApp({
props: {
maxLine: {
type: Number,
default: 2
}
},
data () {
return {
labelArr: [],
isExpand: false,
showExpandBtn: false,
labelLength: 0,
hideLength: 0
}
},
mounted () {
const labels = ['人工智能', '人工智能与应用', '行业分析与市场数据', '标签标签标签标签标签标签标签', '标签A', '啊啊啊', '宝宝贝贝', '微信', '吧啊啊', '哦哦哦哦哦哦哦哦', '人工智能', '人工智能与应用']
this.labelArr = labels
this.labelLength = labels.length
nextTick(() => {
this.init()
})
},
methods: {
init () {
const listCon = document.querySelector('.list-con-3')
const labels = listCon.querySelectorAll('.label:not(.expand-btn)')
const expandBtn = listCon.querySelector('.expand-btn')
let labelIndex = 0 // 渲染到第几个
const listConBottom = listCon.getBoundingClientRect().bottom // 容器底部距视口顶部距离
for(let i = 0; i < labels.length; i++) {
const _top = labels[i].getBoundingClientRect().top
if (_top >= listConBottom ) { // 如果有标签顶部距离超过容器底部则表示超出容器隐藏
this.showExpandBtn = true
console.log('第几个索引标签停止', i)
labelIndex = i
break
} else {
this.showExpandBtn = false
}
}
if (!this.showExpandBtn) {
return
}
nextTick(() => {
const listConRect = listCon.getBoundingClientRect()
const expandBtn = listCon.querySelector('.expand-btn')
const expandBtnWidth = expandBtn.getBoundingClientRect().width
const labelMaringRight = parseInt(window.getComputedStyle(labels[0]).marginRight)
for (let i = labelIndex -1; i >= 0; i--) {
const labelRight = labels[i].getBoundingClientRect().right - listConRect.left
if (labelRight + labelMaringRight + expandBtnWidth <= listConRect.width) {
this.hideLength = i + 1
this.labelLength = this.hideLength
break
}
}
})
},
changeExpand () {
this.isExpand = !this.isExpand
console.log(this.labelLength)
if (this.isExpand) {
this.labelLength = this.labelArr.length
} else {
this.labelLength = this.hideLength
}
}
}
}).mount('#app3')
</script>
解析:同级样式Demo我们使用vue来实现,HTML
布局和CSS
样式没有啥可说的,还是那就话,不行真就送外卖去比较合适,这里我们主要分析一下Javascript
部分,还是先通过getBoundingClientRect()方法来获取容器的bottom和标签的top,通过遍历每个标签来对比是否超出容器,然后我们拿到第一个超出容器的标签序号,就是我们要截断的长度,这里是通过数组的slice()方法来截取标签长度,接下来最关建的如何把按钮拼接上去,因为标签的宽度是不定的,我们要把按钮显示在最后,我们并不确定按钮拼接到最后是不是会导致宽度不够超出,所以我们倒叙遍历标签,如果(最后一个标签的右边到容器的距离right值+标签的margin值+按钮的width)和小于容器宽度,则说明展示隐藏按钮可以直接拼接在后面,否则标签数组长度就要再减一位来判断是否满足。然后展开隐藏功能就通过切换原标签长度和截取的标签长度来完成即可。
方法二:通过与第一个标签左偏移值对比实现
原理:同样式一的方法原理,遍历每个标签然后通过与第一个标签左偏移值对比判断是否超出行数,然后长度截取同方法一一致。
直接上代码:
<script>
const { createApp, nextTick } = Vue
createApp({
data () {
return {
labelList: [],
isExpand: false,
showExpandBtn: false,
labelLength: 0,
hideLength: 0
}
},
mounted () {
const labels = ['人工智能', '人工智能与应用', '行业分析与市场数据报告行业分析与市场数据报告', '标签标签标签标签标签标签标签', '标签A', '啊啊啊', '宝宝贝贝', '微信', '吧啊啊', '哦哦哦哦哦哦哦哦', '人工智能', '人工智能与应用']
this.labelList = labels
this.labelLength = labels.length
nextTick(() => {
this.init()
})
},
methods: {
init () {
const listCon = document.querySelector('.list-con-4')
const labels = listCon.querySelectorAll('.label:not(.expand-btn)')
const firstLabelOffsetLeft = labels[0].getBoundingClientRect().left // 第一个标签左侧偏移量
const labelMaringRight = parseInt(window.getComputedStyle(labels[0]).marginRight)
let line = 0 // 几行
let labelIndex = 0 // 渲染第几个
for(let i = 0; i < labels.length; i++) {
const _offsetLeft = labels[i].getBoundingClientRect().left
if (firstLabelOffsetLeft === _offsetLeft) {
line += 1
}
console.log(line, i)
if (line > 2) {
this.showExpandBtn = true
labelIndex = i
// this.labelLength = this.hideLength
break
} else {
this.showExpandBtn = false
}
}
if (!this.showExpandBtn) {
return
}
nextTick(() => {
const listConRect = listCon.getBoundingClientRect()
const expandBtn = listCon.querySelector('.expand-btn')
console.log(listConRect, expandBtn.getBoundingClientRect())
const expandBtnWidth = expandBtn.getBoundingClientRect().width
for (let i = labelIndex -1; i >= 0; i--) {
console.log(labels[i])
const labelRight = labels[i].getBoundingClientRect().right - listConRect.left
console.log(labelRight, expandBtnWidth, labelMaringRight)
if (labelRight + labelMaringRight + expandBtnWidth <= listConRect.width) {
this.hideLength = i + 1
this.labelLength = this.hideLength
break
}
}
})
},
changeExpand () {
this.isExpand = !this.isExpand
if (this.isExpand) {
this.labelLength = this.labelList.length
} else {
this.labelLength = this.hideLength
}
}
}
}).mount('#app4')
</script>