記錄--多行標簽超出展開摺疊功能

来源:https://www.cnblogs.com/smileZAZ/archive/2023/07/03/17523562.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 記錄分享每一個日常開發項目中的實用小知識,不整那些虛頭巴腦的框架理論與原理,之前分享過抽獎功能、簽字功能等,有興趣的可以看看本人以前的分享。 今天要分享的實用小知識是最近項目中遇到的標簽相關的功能,我不知道叫啥,姑且稱之為【多行標簽 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言

 記錄分享每一個日常開發項目中的實用小知識,不整那些虛頭巴腦的框架理論與原理,之前分享過抽獎功能、簽字功能等,有興趣的可以看看本人以前的分享。  今天要分享的實用小知識是最近項目中遇到的標簽相關的功能,我不知道叫啥,姑且稱之為【多行標簽展開隱藏】功能吧,類似於多行文本展開摺疊功能,如果超過最大行數則顯示展開隱藏按鈕,如果不超過則不顯示按鈕。多行文本展開與摺疊功能在網上有相當多的文章了,也有許多開源的封裝組件,而多行標簽展開隱藏的文章卻比較少,剛好最近我也遇到了這個功能,所以就單獨拿出來與大家分享如何實現。

出處

 【多行標簽展開與隱藏】該功能我們平時可能沒註意一般在哪裡會有,其實最常見的就是各種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>

解析:HTMLCSS同方法一同,不同點在於這裡是通過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倉庫。

本文轉載於:

https://juejin.cn/post/7251394142683742269

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡分享一個我寫的MySQL自動安裝腳本mysql_auto_install.sh,它的功能非常簡單,就是自動化安裝MySQL單實例,讓DBA的工作更簡單、輕鬆一些,不用去手工安裝MySQL實例。從簡單重覆的工作中釋放出來。 下麵是關於mysql_auto_install.sh腳本的一些說明: 1: ...
  • 一丶打開客戶端: 對象資源管理器->管理->維護計劃(右鍵點擊)->維護計劃嚮導 二丶打開後點擊下一步, 填寫名稱與說明並更改備份計劃 三丶點下一步, 選擇維護任務 四丶點擊下一步, 選擇需要備份的資料庫, 和備份文件路徑 五丶點擊下一步, 選擇報告文件保存路徑 六丶點擊下一步, 查看維護計劃, 沒 ...
  • # 一. 事務簡介 **事務是一組操作的集合,它是一個不可分隔的工作單位,事務會把所有的操作作為一個整體一起向系統提交或撤銷操作請求,即這些操作要麼同時成功,要麼同時失敗。** **就比如:張三給李四轉賬1000塊錢,張三銀行賬戶的錢減少了1000,而李四銀行賬戶的錢要增加1000。這一組操作就必須 ...
  • 摘要:DynamoDB是一款托管式的NoSQL資料庫服務,支持多種數據模型,廣泛應用於電商、社交媒體、游戲、IoT等場景。 本文分享自華為雲社區《完全相容DynamoDB協議!GaussDB(for Cassandra)為NoSQL註入新活力》,作者:GaussDB 資料庫 。 DynamoDB是一 ...
  • 摘要: 在SQL Server資料庫中,NULL是表示缺少數據或未知值的特殊標記。處理NULL值是SQL開發人員經常遇到的問題之一。本文將介紹SQL Server中判斷和處理NULL值的不同方法,以及一些解決方案,幫助您更好地處理資料庫中的NULL值情況。 文章內容: 引言: 在資料庫開發中,經常會 ...
  • # 一. 多表關係 - **一對多(多對一)** - **多對一** - **一對一** ## 1. 一對多 ### (1). 案例:部門與員工的關係 ### (2). 關係:一個部門對應多個員工,一個員工對應一個部門 ### (3). 實現:在多的一方建立外建,指向一的一方的主鍵 ![](http ...
  • # 一. 函數 **Mysql中的函數主要分為四類:字元串函數、數值函數、日期函數、流程函數** ## 1. 字元串函數 **常用函數如下:** | 函數 | 功能 | | | | | CONCAT(S1, S2, ......Sn) | 字元串拼接,將S1,S2,.....Sn拼接成一個字元串 | ...
  • 作為一款服務國土調查和自然資源管理工作的一款手機App,是自然資源部自然資源調查監測司組織中國國土勘測規劃院應用互聯網+、雲計算等技術,依托“三調”和年度國土變更調查工程開發的平臺。分為管理版和專業版兩個版本,其中,管理版面向自然資源系統內人員,專業版面向系統外專業技術隊伍。 “國土調查雲”具有土地 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...