下午好,
我需要创建 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>
使用
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>