实现一个用于长列表展开收起的组件

应用场景

  • 当列表内容很长时,需要滑到最底部才能查看下一条数据,这时可以使用该组件的交互来优化使用体验。收起当前一条,继续展开查看下一条数据。

组件思路

判断同时满足以下两个条件时,悬浮展示收起按钮

  1. 展开的内容的盒子的左上角坐标的Y值即top + 收起按钮的高度 <= 当前浏览器窗口的可视区域高度clientHeight
  2. 展开的内容的盒子的右下角坐标的Y值即bottom > 当前浏览器窗口的可视区域高度clientHeight
javascript 复制代码
<template>
  <div class="collapse-item">
    <div class="source">
      <slot name="source"></slot>
    </div>
    <div class="collapse-panel">
      <div class="panel">
        <div
          class="panel-content"
          ref="panel-content"
          :style="{ '--panelMinHeight': minHeight + 'px', '--panelHeight': panelHeight }"
          :class="isExpanded ? 'active' : ''"
        >
          <slot></slot>
        </div>
        <div
          class="expand-btn height-96 flex-center"
          @click="togglePanel"
          :class="{ 'is-fixed': fixedControl }"
          v-if="isExpandShow"
        >
          <span class="color-main">{{ isExpanded ? "收起" : "展开" }}</span>
          <img
            src="@/assets/databoard/arrow-fold.png"
            class="expand-icon width-24 height-22 m-l-10"
            :class="!isExpanded ? '' : 'icon-reverse'"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "CollapseItem",
  props: {
    minHeight: {
      type: Number,
      default: 0
    },
    isExpandShow: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      isExpanded: false,
      panelHeight: 0, // 展开内容高度
      fixedControl: false, // 固定收起按钮控制
      scrollTop: 0 // 展开之前的滚动位置
    };
  },
  watch: {
    isExpanded(val) {
      if (!val) {
        this.fixedControl = false;
        this.removeScrollHandler();
        window.scrollTo({
          top: this.scrollTop
        });
        return;
      }
      const timer = setTimeout(() => {
        window.addEventListener("scroll", this.scrollHandler);
        this.scrollHandler();
        clearTimeout(timer);
      }, 100);
    }
  },
  methods: {
    // 切换展开收起状态
    togglePanel() {
      if (!this.panelHeight) {
        const panel = this.$refs["panel-content"];
        const panelHeight = panel.scrollHeight;
        this.panelHeight = panelHeight + "px";
      }
      this.isExpanded = !this.isExpanded;
      this.$nextTick(() => {
        this.scrollTop = document.documentElement.scrollTop;
      });
    },
    // 监听滚动事件,控制收起按钮展示和隐藏
    scrollHandler() {
      const { top, bottom } = this.$refs["panel-content"].getBoundingClientRect();
      const clientHeight = document.documentElement.clientHeight;
      this.fixedControl = bottom > clientHeight && top + 48 <= clientHeight;
    },
    // 移除滚动事件
    removeScrollHandler() {
      window.removeEventListener("scroll", this.scrollHandler);
    }
  },
  beforeDestroy() {
    this.removeScrollHandler();
  }
};
</script>
<style lang="less" scoped>
.collapse-panel {
  .panel-content {
    width: 100%;
    height: var(--panelMinHeight);
    overflow-y: hidden;
    overflow-x: visible;
    transition: height 0.2s;
  }

  .active {
    height: var(--panelHeight);
  }
  .expand-btn {
    .expand-icon {
      transition: all 0.2s;
    }
    .icon-reverse {
      transform: rotate(180deg);
    }
  }
  .is-fixed {
    position: fixed;
    bottom: 0px;
    background: #fff;
    width: 100%;
    left: 0;
    box-shadow: 0px -8px 24px 1px rgba(0, 0, 0, 0.1);
  }
}
</style>
相关推荐
小研说技术5 分钟前
实时通信对比,一场MCP协议的技术革命
前端·后端·面试
kyriewen9 分钟前
React Hooks原理:为什么不能写在if里?揭开Hook的“魔法”面纱
前端·react.js·前端框架
敲代码的彭于晏11 分钟前
Claude Code Token 烧得太快?这8个方案帮你立省90%!
前端·ai编程·claude
可视之道13 分钟前
设备拓扑图中的实时状态映射与动画策略:告警闪烁、流向动画、质量码怎么共存
前端
涂兵兵_青石疏影14 分钟前
绘制图像-clip方法
前端
焦糖玛奇朵婷32 分钟前
解锁扭蛋机小程序的五大优势
java·大数据·服务器·前端·小程序
SwJieJie43 分钟前
windsurf的配置和项目规则、工作流、agent技巧使用
前端
白日梦想家6811 小时前
从基础入手,分清一次性定时器与永久定时器
前端
AIwork4me1 小时前
别再把 RAG 当知识库:用 AutoClaw 搭一套会进化的 Karpathy LLM Wiki
前端
彩票管理中心秘书长1 小时前
Git 归档与补丁命令大全(完整详解版)
前端