這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 記錄分享每一個日常開發項目中的實用小知識,不整那些虛頭巴腦的框架理論與原理,之前分享過抽獎功能、簽字功能等,有興趣的可以看看本人以前的分享。 今天要分享的實用小知識是最近項目中遇到的標簽相關的功能,我不知道叫啥,姑且稱之為【多行標簽 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
記錄分享每一個日常開發項目中的實用小知識,不整那些虛頭巴腦的框架理論與原理,之前分享過抽獎功能、簽字功能等,有興趣的可以看看本人以前的分享。 今天要分享的實用小知識是最近項目中遇到的標簽相關的功能,我不知道叫啥,姑且稱之為【多行標簽展開隱藏】功能吧,類似於多行文本展開摺疊功能,如果超過最大行數則顯示展開隱藏按鈕,如果不超過則不顯示按鈕。多行文本展開與摺疊功能在網上有相當多的文章了,也有許多開源的封裝組件,而多行標簽展開隱藏的文章卻比較少,剛好最近我也遇到了這個功能,所以就單獨拿出來與大家分享如何實現。
出處
【多行標簽展開與隱藏】該功能我們平時可能沒註意一般在哪裡會有,其實最常見的就是各種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>
這裡也無需多做解釋了,直接看代碼即可。
結尾
上面就是【多行標簽展開隱藏】功能的基本實現原理,網上相關實現比較少,我也是只用了Javascript來實現,如果可以純靠CSS實現,有更簡單或更好的方法實現可以留言相互交流學。代碼沒有封裝成組件,但是具有一些參考意義,用於生產可以自己去封裝成組件使用,完整的代碼在我的GitHub倉庫。