vue3+elementPlus实现循环列表内容超出时展开收起功能

循环列表内容超出时隐藏部分,点击阅读全文展开,内容不超出不显示该按钮。

javascript 复制代码
<template>
  <div class="collapse-list-container">
    <el-card shadow="hover" class="list-card">
      <el-list border :data="listData" class="content-list">
        <el-list-item 
          v-for="(item, index) in listData" 
          :key="index" 
          class="list-item"
        >
          <!-- 标题 -->
          <div class="item-title">{{ item.title }}</div>
          
          <!-- 内容区域:仅渲染一次 -->
          <div 
            class="item-content" 
            ref="contentRefs"
            :style="{
              height: item.isExpand ? 'auto' : `${twoLineHeight}px`,
              maxHeight: item.isExpand ? 'none' : `${twoLineHeight}px`
            }"
          >
            {{ item.content }}
          </div>
          
          <!-- 仅内容超两行时显示按钮 -->
          <el-button 
            v-if="item.isOverTwoLine"
            type="text" 
            class="toggle-btn"
            @click="toggleExpand(index)"
          >
            {{ item.isExpand ? '收起' : '阅读全文' }}
          </el-button>
        </el-list-item>
      </el-list>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick, computed } from 'vue'

// 基础样式参数(与CSS保持一致,保证计算精准)
const fontSize = 14 // 字体大小(px)
const lineHeight = 1.6 // 行高倍数
// 计算两行文本的高度(行高*字体大小*行数)
const twoLineHeight = computed(() => lineHeight * fontSize * 2)

// 内容DOM引用
const contentRefs = ref([])

// 模拟列表数据(移除冗余字段,初始化isOverTwoLine为false)
const listData = ref([
  {
    title: '列表项1(超过两行)',
    content: 'Vue3 是一套用于构建用户界面的渐进式框架,与其他大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。',
    isExpand: false,
    isOverTwoLine: false
  },
  {
    title: '列表项2(刚好两行)',
    content: 'Element Plus 是基于 Vue 3,面向设计师和开发者的组件库。Element Plus 延续了 Element UI 的设计理念',
    isExpand: false,
    isOverTwoLine: false
  },
  {
    title: '列表项3(一行内容)',
    content: '多行文本折叠是中后台系统常见的交互需求',
    isExpand: false,
    isOverTwoLine: false
  }
])

// 切换展开/收起状态
const toggleExpand = (index) => {
  listData.value[index].isExpand = !listData.value[index].isExpand
}

/**
 * 计算文本真实高度(核心优化:纯JS计算,无DOM克隆)
 * 原理:创建临时DOM,计算高度后立即销毁,不影响页面渲染
 */
const calculateTextHeight = (text, containerWidth) => {
  // 创建临时div(仅在内存中,不挂载到DOM)
  const tempDiv = document.createElement('div')
  // 设置与真实内容完全一致的样式(保证计算精准)
  tempDiv.style.cssText = `
    font-size: ${fontSize}px;
    line-height: ${lineHeight};
    width: ${containerWidth}px;
    position: absolute;
    visibility: hidden;
    height: auto;
    overflow: hidden;
    white-space: normal;
  `
  // 赋值文本(仅一次)
  tempDiv.textContent = text
  // 临时挂载到body(必须挂载才能计算高度)
  document.body.appendChild(tempDiv)
  // 获取真实高度
  const height = tempDiv.offsetHeight
  // 立即销毁临时DOM,避免内存泄漏
  document.body.removeChild(tempDiv)
  return height
}

// 初始化判断每个列表项是否超过两行
const initContentStatus = () => {
  nextTick(() => {
    contentRefs.value.forEach((contentRef, index) => {
      if (!contentRef) return
      // 获取容器宽度(保证计算的高度与实际显示一致)
      const containerWidth = contentRef.offsetWidth
      // 获取文本内容
      const text = listData.value[index].content
      // 计算文本真实高度
      const realHeight = calculateTextHeight(text, containerWidth)
      // 判断是否超过两行,并更新状态
      listData.value[index].isOverTwoLine = realHeight > twoLineHeight.value
    })
  })
}

// 组件挂载后初始化
onMounted(() => {
  initContentStatus()
})

// 可选:监听窗口大小变化,重新计算(适配响应式场景)
// import { onMounted, onUnmounted } from 'vue'
// let resizeObserver
// onMounted(() => {
//   initContentStatus()
//   resizeObserver = new ResizeObserver(initContentStatus)
//   contentRefs.value.forEach(ref => ref && resizeObserver.observe(ref))
// })
// onUnmounted(() => {
//   resizeObserver?.disconnect()
// })
</script>

<style scoped>
.collapse-list-container {
  width: 800px;
  margin: 20px auto;
}

.list-card {
  padding: 20px;
}

.content-list {
  --el-list-item-padding: 16px 0;
}

.list-item {
  border-bottom: 1px solid #ebeef5;
  padding-bottom: 16px;
  margin-bottom: 16px;
}

.list-item:last-child {
  border-bottom: none;
  margin-bottom: 0;
}

.item-title {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 8px;
  color: #1f2937;
}

/* 核心:可视内容区域 */
.item-content {
  font-size: 14px; /* 与JS中fontSize一致 */
  color: #4b5563;
  line-height: 1.6; /* 与JS中lineHeight一致 */
  overflow: hidden;
  transition: height 0.3s ease-in-out; /* 流畅动画 */
  width: 100%;
  position: relative;
}

/* 折叠状态添加省略号 */
.item-content:not([style*="auto"])::after {
  content: '...';
  position: absolute;
  right: 0;
  bottom: 0;
  background-color: #ffffff;
  padding-left: 8px;
}

.toggle-btn {
  color: #409eff;
  padding: 0;
  margin-top: 8px;
}

.toggle-btn:hover {
  color: #66b1ff;
}
</style>
相关推荐
wscats3 小时前
Markdown 编辑器技术调研
前端·人工智能·markdown
EnoYao3 小时前
Markdown 编辑器技术调研
前端·javascript·人工智能
JIngJaneIL3 小时前
基于java+ vue医院管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
计算机学姐3 小时前
基于SpringBoot的高校论坛系统【2026最新】
java·vue.js·spring boot·后端·spring·java-ee·tomcat
JIngJaneIL3 小时前
基于java + vue校园跑腿便利平台系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
前端要努力3 小时前
月哥创业3年,还活着!
前端·面试·全栈
sao.hk4 小时前
ubuntu2404安装k3s
前端·chrome
cos4 小时前
Worktrunk 完全指南:让 Git Worktree 和 Claude Code 和平共处
前端·ai编程·claude
不哦罗密经4 小时前
python相关
服务器·前端·python