CSS:当容器大小固定时,`flex` 手风琴中的展开/折叠动画

问题描述 投票:0回答:1

下午好,

我需要创建 HTML/CSS/JS 手风琴。要求:手风琴容器的高度必须固定(100% body 的高度),如果面板的内容溢出,滚动条必须位于面板的内容 div 内部,而不是位于手风琴容器本身内部! 我创建了一支笔:

https://codepen.io/lotrmj/pen/bGJEzrp
来展示总体想法。一切都按预期进行,但没有动画......

是否可以为展开/折叠操作创建类似于以下内容的动画: https://vuetifyjs.com/en/components/expansion-panels/#variant? 或者是不可能的,因为面板标题和相应的内容没有固定的高度? 如果可以的话,有人可以帮我一下吗?

我之前已经使用 jQuery UI 库做到了这一点,并且它可以工作,但我正在寻找更现代的解决方案/库..

我尝试寻找示例,但没有找到任何具有类似问题的示例。

<template>
  <div id="nav">
      <template v-for="i in 5">
        <div class="panel-header">
          <span>PANEL {{i}}</span>
          <button @click="expandOrCollapse(i)">{{i == activePanel ? "Collapse" : "Expand"}}</button>
        </div>
        <div class="panel-content" v-if="i == activePanel">
          <div class="content">
            Very long panel {{i}} content with scrollbar...
          </div>
        </div>
      </template>
  </div>
  <div id="main">MAIN DIV</div>
</template>

<script>
export default {
  data() {
    return {
      activePanel: 2
    };
  },
  methods: {
    expandOrCollapse(i) {
      if (i == this.activePanel) {
        this.activePanel = undefined;
      } else {
        this.activePanel = i;
      }
    }
  }
};
</script>

<style>
  html, body {
    height: 100%;
    margin: 0;
  }
  body {
    display: flex;
  }
  #app {
    flex-grow: 1;
    display: flex;
  }
  #nav {
   width: 400px;
   border-right: 1px solid #ccc;
   overflow: auto;
   display: flex;
   flex-direction: column;
  }
  #main {
   flex-grow: 1;
   display: flex;
   align-items: center;
   justify-content: center;
  }
  .panel-header, .panel-content {
    border-bottom: 1px solid #ddd;
    padding: 5px;
  }
  .panel-header span {
    padding-right: 3px;
  }
  .panel-content {
    overflow: auto;
    flex-grow: 1;
  }
  .content {
    height: 1000px;
  }
</style>
javascript css vue.js animation accordion
1个回答
0
投票

使用

v-if
意味着该元素已从 DOM 中删除,这不会给任何显示动画的机会。

您可以考虑使用 CSS 网格布局,其中每个面板标题和内容占据一个网格行。然后,CSS 在

0fr
1fr
之间转换,分别隐藏或显示面板内容。隐藏时,我们还需要删除内容的
padding-top
padding-bottom

Vue.createApp({
  data() {
    return {
      activePanel: 2
    };
  },
  computed: {
    templateRows() {
      return Array(5)
        .fill()
        .flatMap((_, i) => ['max-content', (i + 1) === this.activePanel ? '1fr' : '0fr'])
        .join(' ');
    },
  },
  methods: {
    expandOrCollapse(i) {
      if (i == this.activePanel) {
        this.activePanel = undefined;
      } else {
        this.activePanel = i;
      }
    }
  }
}).mount('#app');
html, body {
  height: 100%;
  margin: 0;
}
body {
  display: flex;
}
#app {
  flex-grow: 1;
  display: flex;
}
#nav {
 width: 400px;
 border-right: 1px solid #ccc;
 overflow: auto;
 display: grid;
 transition: grid-template-rows 500ms;
}
#main {
 flex-grow: 1;
 display: flex;
 align-items: center;
 justify-content: center;
}
.panel-header, .panel-content {
  border-bottom: 1px solid #ddd;
  padding: 5px;
}
.panel-header span {
  padding-right: 3px;
}
.panel-content {
  overflow: auto;
  flex-grow: 1;
  transition: padding-top 500ms, padding-bottom 500ms;
}
.panel-content:not([aria-expanded="true"]) {
  padding-top: 0;
  padding-bottom: 0;
}
.content {
  height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.21/vue.global.prod.min.js" integrity="sha512-tltvjJD1pUnXVAp0L9id/mcR+zc0xsIKmPMJksEclJ6uEyI8D6eZWpR0jSVWUTXOKcmeBMyND+LQH4ECf/5WKg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="app">
  <div id="nav" :style="{ gridTemplateRows: templateRows }">
      <template v-for="i in 5">
        <div class="panel-header">
          <span>PANEL {{i}}</span>
          <button @click="expandOrCollapse(i)">{{i == activePanel ? "Collapse" : "Expand"}}</button>
        </div>
        <div class="panel-content" :aria-expanded="i == activePanel">
          <div class="content">
            Very long panel {{i}} content with scrollbar...
          </div>
        </div>
      </template>
  </div>
  <div id="main">MAIN DIV</div>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.