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

应用场景

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

组件思路

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

  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>
相关推荐
Cutecat_2 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
qq_422152572 小时前
PDF 加水印工具怎么选?2026 年文档版权保护方案对比
前端·pdf·github
kyriewen2 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
brucelee1863 小时前
OpenClaw 浏览器控制(Chrome MCP)完整教程
前端·chrome
ct9783 小时前
React 状态管理方案深度对比
开发语言·前端·react
胡志辉的博客4 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖4 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty4 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
AI焦点4 小时前
跨越协议鸿沟:Tool Use状态机从Anthropic到OpenAI兼容体系的适配要点
前端·人工智能
Dxy12393102164 小时前
Python线程锁:为什么多线程会“打架“,以及怎么解决
开发语言·前端·python