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

应用场景

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

组件思路

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

  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>
相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Csvn5 小时前
性能监控
前端
Csvn6 小时前
构建工具
前端
FRing036 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷7 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen8 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒9 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫9 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus10 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
孟陬11 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端