CSS flex布局 列表单个元素点击 本行下插入详情独占一行
技术栈:Vue2 + javaScript
简介
在实际开发过程中有遇到一个场景:一个list,每行个数固定,点击单个元素后,在当前行与下一行之间插入一行元素详情,便于更直观的查看到对应的数据详情。
这种情形,在移动端比较常见,比如用户列表,点击单个列表 展示详情,可以考虑 flex 布局 + position relative 定位。
实现
思路
对于需求重点和实现拆解
- 列表元素:for 遍历
- 每行固定(3)个元素:flex布局、宽度%
- 详情在该元素下独占一行:for 内元素、position relative
核心代码
mock数据
// list数据
list: [
{ id: 1, name: '测试数据1', desc: '测试数据1描述测试数据1描述测试数据1描述测试数据1描述' },
{ id: 2, name: '测试数据2', desc: '测试数据2描述测试数据2描述测试数据2描述测试数据2描述' },
{ id: 3, name: '测试数据3', desc: '测试数据3描述测试数据3描述测试数据3描述测试数据3描述' },
{ id: 4, name: '测试数据4', desc: '测试数据4描述测试数据4描述测试数据4描述测试数据4描述' },
{ id: 5, name: '测试数据5', desc: '测试数据5描述测试数据5描述测试数据5描述测试数据5描述' },
{ id: 6, name: '测试数据6', desc: '测试数据6描述测试数据6描述测试数据6描述测试数据6描述' },
{ id: 7, name: '测试数据7', desc: '测试数据7描述测试数据7描述测试数据7描述测试数据7描述' },
{ id: 8, name: '测试数据8', desc: '测试数据8描述测试数据8描述测试数据8描述测试数据8描述' },
{ id: 9, name: '测试数据9', desc: '测试数据9描述测试数据9描述测试数据9描述测试数据9描述' },
{ id: 10, name: '测试数据10', desc: '测试数据10描述测试数据10描述测试数据10描述测试数据10描述' },
{ id: 11, name: '测试数据11', desc: '测试数据11描述测试数据11描述测试数据11描述测试数据11描述' }
],
showDetail: false, // 是否显示详情
detail: {}, // 详情数据
DOM结构
<!-- 列表容器 -->
<div class="container">
<!-- 单个元素 start-->
<div v-for="(item, index) in list" :key="index" class="item-box" @click="toggleEvent(item)">
<div class="item-name">{{ item.name }}</div>
<!-- 详情 start -->
<div class="item-detail" v-if="showDetail && detail.id == item.id" :style="caculateDetailLeft(index)">
<!-- 气泡三角 -->
<div class="top-jian" :style="caculateJianLeft(index)"></div>
<!-- 详情描述 -->
<div>{{ detail.desc }}</div>
</div>
<!-- 详情 end -->
</div>
<!-- 单个元素 end -->
</div>
CSS 与 动态位移
.container {
width: 80vw; // 列表固定宽度
display: flex;
gap: 16px; // 元素间距
flex-wrap: wrap;
.item-box {
// calc((父元素宽度 - 间距*(每行个数-1)) / 每行个数)
width: calc((100% - 32px) / 3);
.item-detail {
width: 80vw; // 列表固定宽度
position: relative;
background: #AFF050;
.top-jian {
width: 20px;
height: 20px;
position: absolute;
background: #AFF050;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
top: -6px;
}
}
}
}
caculateDetailLeft(index) {
return {
// calc(calc(calc(100% + 16px) * ${index%3}) * -1)
// calc(-1 * (100% + 间距) * ${index % 每行个数})
left: `calc(-1 * (100% + 16px) * ${index % 3})`
}
},
caculateJianLeft(index) {
return {
// calc(calc(calc((100% - 32px) / 3) * ${index%3}) + calc((100% - 32px) / 6))
// calc((100% - 间距*2) * (${index % 每行个数} / 每行个数 + 1 / (每行个数*2)))
left: `calc((100% - 32px) * (${index % 3} / 3 + 1 / 6))`
}
}
效果
完整代码
<template>
<div class="container">
<div v-for="(item, index) in list" :key="index" class="item-box" @click="toggleEvent(item)">
<div class="item-name">{{ item.name }}</div>
<div class="item-detail" v-if="showDetail && detail.id == item.id" :style="caculateDetailLeft(index)">
<div class="top-jian" :style="caculateJianLeft(index)"></div>
<div>
{{ detail.desc }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Test',
data() {
return {
list: [
{ id: 1, name: '测试数据1', desc: '测试数据1描述测试数据1描述测试数据1描述测试数据1描述' },
{ id: 2, name: '测试数据2', desc: '测试数据2描述测试数据2描述测试数据2描述测试数据2描述' },
{ id: 3, name: '测试数据3', desc: '测试数据3描述测试数据3描述测试数据3描述测试数据3描述' },
{ id: 4, name: '测试数据4', desc: '测试数据4描述测试数据4描述测试数据4描述测试数据4描述' },
{ id: 5, name: '测试数据5', desc: '测试数据5描述测试数据5描述测试数据5描述测试数据5描述' },
{ id: 6, name: '测试数据6', desc: '测试数据6描述测试数据6描述测试数据6描述测试数据6描述' },
{ id: 7, name: '测试数据7', desc: '测试数据7描述测试数据7描述测试数据7描述测试数据7描述' },
{ id: 8, name: '测试数据8', desc: '测试数据8描述测试数据8描述测试数据8描述测试数据8描述' },
{ id: 9, name: '测试数据9', desc: '测试数据9描述测试数据9描述测试数据9描述测试数据9描述' },
{ id: 10, name: '测试数据10', desc: '测试数据10描述测试数据10描述测试数据10描述测试数据10描述' },
{ id: 11, name: '测试数据11', desc: '测试数据11描述测试数据11描述测试数据11描述测试数据11描述' }
],
showDetail: false,
detail: {},
}
},
methods: {
toggleEvent(item) {
if (item.id == this.detail.id) {
this.showDetail = !this.showDetail
} else {
this.showDetail = true
this.detail = { ...item }
}
},
caculateDetailLeft(index) {
return {
// calc(calc(calc(100% + 16px) * ${index%3}) * -1)
left: `calc(-1 * (100% + 16px) * ${index % 3})`
}
},
caculateJianLeft(index) {
return {
// calc(calc(calc((100% - 32px) / 3) * ${index%3}) + calc((100% - 32px) / 6))
left: `calc((100% - 32px) * (${index % 3} / 3 + 1 / 6))`
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
width: 80vw;
display: flex;
gap: 16px;
flex-wrap: wrap;
.item-box {
width: calc((100% - 32px) / 3);
.item-name {
border: 1px solid #ccc;
padding: 30px;
text-align: center;
border-radius: 15px;
}
.item-detail {
width: 80vw;
position: relative;
background: #AFF050;
margin-top: 16px;
padding: 30px;
border-radius: 15px;
.top-jian {
width: 20px;
height: 20px;
position: absolute;
background: #AFF050;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
top: -6px;
}
}
}
}
</style>