Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
?記錄分享每一個日常開發項目中的實用小知識,不整那些虛頭巴腦的框架理論與原理,之前分享過抽獎功能、簽字功能等,有興趣的可以看看本人以前的分享。 ?今天要分享的實用小知識是最近項目中遇到的標簽相關的功能,我不知道叫啥,姑且稱之為【多行標簽展開隱藏】功能吧,類似于多行文本展開折疊功能,如果超過最大行數則顯示展開隱藏按鈕,如果不超過則不顯示按鈕。多行文本展開與折疊功能在網上有相當多的文章了,也有許多開源的封裝組件,而多行標簽展開隱藏的文章卻比較少,剛好最近我也遇到了這個功能,所以就單獨拿出來與大家分享如何實現。
?【多行標簽展開與隱藏】該功能我們平時可能沒注意一般在哪里會有,其實最常見的就是各種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>
這里也無需多做解釋了,直接看代碼即可。
原文鏈接:https://juejin.cn/post/7251394142683742269
SS圓形展開菜單。
·1.設置開關狀態為checked時子菜單的展開收縮動畫。
·2.設置開關狀態為checked時子菜單的展開收縮動畫。
·3.設置開關狀態為checked時子菜單的展開收縮動畫。
·4.重置開關細節樣式。
·5.添加子菜單的詳細樣式。
·6.設置開關狀態為checked時子菜單的展開收縮動畫。
·7.設置開關狀態為checked時子菜單的展開收縮動畫。
ue可以通過v-if和v-show指令來實現收縮展開的效果。
v-if是根據條件來決定是否渲染元素,當條件為真時,元素會被渲染;當條件為假時,元素會被從DOM中移除。因此,可以通過控制條件的真假來實現收縮展開的效果。
v-show是根據條件來決定元素的顯示與隱藏,當條件為真時,元素會顯示;當條件為假時,元素會隱藏。因此,可以通過控制條件的真假來實現收縮展開的效果。
具體實現可以參考以下代碼:
```html
<template>
<div>
<button @click="toggle">收縮/展開</button>
<div v-if="isExpanded">
<!-- 展開內容 -->
</div>
<div v-show="isExpanded">
<!-- 展開內容 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
isExpanded: false
};
},
methods: {
toggle() {
this.isExpanded=!this.isExpanded;
}
}
};
</script>
``在上述代碼中,我們定義了一個isExpanded的data屬性,用于控制展開與收縮的狀態。初始狀態為false,表示收縮狀態。
在按鈕的點擊事件中,通過toggle方法來切換isExpanded的值,從而實現收縮和展開的效果。
在v-if指令中,根據isExpanded的值來決定是否渲染元素。
在v-show指令中,根據isExpanded的值來決定元素的顯示與隱藏。
v-if是惰性的,即在條件為假時,元素不會被渲染到DOM中。而v-show是通過CSS的display屬性來控制元素的顯示與隱藏,因此即使條件為假,元素仍然會存在于DOM中,只是不可見而已。
根據實際需求,可以選擇使用v-if還是v-show來實現收縮展開的效果。如果需要頻繁切換展開與收縮,且展開內容較多,建議使用v-show;如果展開內容較少,且切換頻率較低,建議使用v-if。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。