当前位置: 首页 > news >正文

CSS flex布局 列表单个元素点击 本行下插入详情独占一行

技术栈:Vue2 + javaScript

简介

在实际开发过程中有遇到一个场景:一个list,每行个数固定,点击单个元素后,在当前行与下一行之间插入一行元素详情,便于更直观的查看到对应的数据详情。
这种情形,在移动端比较常见,比如用户列表,点击单个列表 展示详情,可以考虑 flex 布局 + position relative 定位。

实现

思路

对于需求重点和实现拆解

  1. 列表元素:for 遍历
  2. 每行固定(3)个元素:flex布局、宽度%
  3. 详情在该元素下独占一行: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>

相关文章:

  • BMS项目-面试及答疑整理
  • 【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发三
  • Linux:线程的互斥与同步
  • Vmware ubuntu22.04 虚拟机 连接windows主机虚拟串口
  • 5G时代的运维变革与美信监控易的深度剖析
  • 【漫话机器学习系列】093.代价函数和损失函数(Cost and Loss Functions)
  • 网络安全扫IP工具
  • QT基础二、信号和槽
  • Python常见面试题的详解8
  • Javascript网页设计案例:通过PDF.js实现一款PDF阅读器,包括预览、页面旋转、页面切换、放大缩小、黑夜模式等功能
  • 【油猴脚本/Tampermonkey】DeepSeek 服务器繁忙无限重试(20250217优化)
  • 鸿蒙NEXT开发-界面渲染(条件和循环)
  • DeepSeek-R1论文阅读及蒸馏模型部署
  • 华三交换机-链路聚合配置案例
  • Vue2 中使用 UniApp 时,生命周期钩子函数总结
  • 【SpringBoot整合系列】SpringBoot3.x整合Swagger
  • txt文件批量转PDF
  • 为什么要选择3D机器视觉检测
  • Python学习之网络编程
  • PostgreSQL:备库的延迟问题处理步骤
  • 中国强镇密码丨洪泽湖畔的蒋坝,如何打破古镇刻板印象
  • 200枚篆刻聚焦北京中轴线,“印记”申遗往事
  • 中国公民在日本被机动车碾压身亡,我使馆发布提醒
  • 三大白电巨头去年净利近900亿元:美的持续领跑,格力营收下滑
  • 卸任兰大校长后,严纯华院士重返北大作报告
  • 历史新高!上海机场一季度营收增至31.72亿元,净利润增34%