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

书架效果的实现

🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣

1. 对齐目标

前端想实现一个类似的书架放置书籍的效果,目标如下:

ScreenShot_2026-02-09_171838_726

2. 思路梳理

我们使用的技术栈:vue

实现这样的一个效果,我们需要知道以下信息:

  1. 每行可以放置多少书本?
  2. 放下所有的书本需要多少行?
  3. 需要什么样的数据结构?

我们现在一个个来思考,既然我们选择了vue来实现,秉持着数据驱动视图的理念,我们先从需要什么样的数据结构进行入手,其实很简单,只需要一个二维数组就可以了。

二维数组的第一层就是书架的每一行,二维数组的第二层就是每一行对应的书本

[   [       {id:1,,name:"语文课本1"},//每一行放置的课本       {id:2,name:"语文课本2"},   ],     [       {id:3,,name:"语文课本1"},//第二行放置的课本       {id:4,name:"语文课本2"},   ], 
]

那么我们就可以按照这样的一个数据结构来遍历展示即可。

3. 实现步骤

3.1 界面实现

我们可以先按照我们上面已经写好的数据,来写好对应的Html和css,然后将效果渲染出来。

<template>   <div class="shelf">       <div class="shlef-row" v-for="(row, rowIndex) in bookData" :key="rowIndex">           <div class="book-item" v-for="book in row" :key="book.id">               {{ book.bookName }}           </div>       </div>   </div>
</template>
​
<script setup>
import { ref } from 'vue';
​
const bookData = ref([   [       { id: 1, bookName: "语文课本1" },       { id: 2, bookName: "语文课本2" },   ],   [       { id: 3, bookName: "语文课本1" },//第二行放置的课本       { id: 4, bookName: "语文课本2" },   ]
])
</script>
​
<style>
.shelf {   width: 1200px;   height: auto;   border: 1px solid #ccc;   margin: 0 auto;
}
​
.shlef-row {   width: 100%;   margin: 0 0 20px 0;   display: flex;   border-bottom: 2px solid orange;
}
​
.shlef-row:last-child {   margin-bottom: 0;
}
​
.book-item {   box-sizing: border-box;   padding: 10px;   margin-right: 20px;   width: 130px;   height: 160px;   color: #fff;   background-color: skyblue;
}
</style>

3.2 根据真实的数据构造页面数据

我们在真实的环境下,肯定是通过接口获取到真实的后端数据,后端给我们的数据可能并不是我们想要的,我们就要对后端的数据进行构造,我们先分析下我们获取到真实的后端数据,来做一下分析。

[       { id: 1, bookName: "语文课本1" },       { id: 2, bookName: "语文课本2" },       { id: 3, bookName: "数学课本1" },       { id: 4, bookName: "数学课本2" },       { id: 5, bookName: "数学课本3" },       { id: 6, bookName: "数学课本4" },       { id: 7, bookName: "化学课本1" },       { id: 8, bookName: "化学课本2" },       { id: 9, bookName: "化学课本1" },       { id: 10, bookName: "化学课本2" },       { id: 11, bookName: "物理课本1" },       { id: 12, bookName: "物理课本2" },       { id: 13, bookName: "物理课本3" },       { id: 14, bookName: "物理课本4" },       { id: 15, bookName: "生物课本1" },       { id: 16, bookName: "生物课本2" }
]

可以看出,后端的数据给我们的是一整个数组,那么对于我们来说就需要解决以下问题:

  • 计算一行可以放置多少本书
  • 计算总共多少行

每行可以放置书本数:Math.floor(书架宽度 / 每本书实际占据的宽度(包含margin))

总共多少行书架:Math.ceil(书本总数 / 每行可以放置的书本树)

截取数组:循环书架行数,然后不停的从后端数据中去截取对应数量数据即可。

// 构造页面数据 rawData:后端数据
const genBookData = (rawData) => {   const counts = Math.floor(1200 / 150);//每行可放置书本数fam   const rowCount = Math.ceil(rawData.length / counts);//总共有多少行   const rowArr = [];//书架二维数组
​   for (let i = 0; i < rowCount; i++) {       //每次截取对应的书本,添加到二维数组       const rowBooks = rawData.slice(i * counts, (i + 1) * counts);       rowArr.push(rowBooks);   }   return rowArr;
}

其实,这个时候,就已经实现了基本的书架功能了。

ScreenShot_2026-02-09_172000_447

 

4. 附加功能优化

上面虽然已经实现了基本的书架效果,但是我们面临以下的问题:

  • 现在最后一本书距离右侧空间太大,我想让书本平分空间。
  • 当用户改变浏览器窗口,我对应的书架宽度改变了,需要去根据屏幕更新每行放置的书本数。

1. 书本平分空间遇到的问题

对于评分空间,大家一定觉得很容易处理,直接使用flex布局,让每本书flex:1平分空间即可。

但是这里我重点想说的是,如果最后一行书架的书本如果放不满书架,那么就会受到flex:1的影响,自动撑大宽度,导致和上一行的书本宽度不一致。效果如下:

ScreenShot_2026-02-09_172019_674

解决方法:就是添加一些虚拟的占位元素(placeholder),我们改动一下我们的构造数据的函数。
// 构造页面数据
const genBookData = (rawData) => {   const counts = Math.floor(1200 / 150);//每行可放置书本数fam   const rowCount = Math.ceil(rawData.length / counts);//总共有多少行   const rowArr = [];//书架二维数组
​   for (let i = 0; i < rowCount; i++) {       const rowBooks = rawData.slice(i * counts, (i + 1) * counts);       //+++       if (i === rowCount - 1 && rowBooks.length < counts) {           // 当这一行实际的书本数 < 每行能放置的书本数时     向二维数组中添加占位元素           const placeholders = Array(counts - rowBooks.length).fill().map((_, index) => ({               id: `placeholder-${index}`,               isPlaceholder: true           }));           rowArr.push([...rowBooks, ...placeholders]);       } else {           rowArr.push(rowBooks);       }   }   return rowArr;
}

这样就正常了,大家可以把占位元素直接给隐藏( visibility: hidden;)即可

2. 解决动态计算问题

动态计算的时候其实也很简单,我们只需要获取到当前书架的宽度,然后监听windowresize事件,再去重新执行我们的构造数据的逻辑即可。

但是我有一个更好的方法,使用计算属性! 我们计算属性中依赖一下我们当前屏幕宽度的变量(shelfWidth),这样我们在改变屏幕的时候,直接更新shelfWidth即可,然后计算属性会自动执行,重新计算我们的数据。直接看最终代码。

<template>   <div class="shelf" ref="shelfRef">       <div class="shlef-row" v-for="(row, rowIndex) in bookData" :key="rowIndex">           <div class="book-item" v-for="book in row" :key="book.id">               {{ book.bookName }}           </div>       </div>   </div>
​   <button @click="changeWidtn">改变宽度</button>
</template>
​
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
​
const changeWidtn = () => {   shelfWidth.value = 900;
}
​
// 请求接口的数据
const apiData = [   { id: 1, bookName: "语文课本1" },   { id: 2, bookName: "语文课本2" },   { id: 3, bookName: "数学课本1" },   { id: 4, bookName: "数学课本2" },   { id: 5, bookName: "数学课本3" },   { id: 6, bookName: "数学课本4" },   { id: 7, bookName: "化学课本1" },   { id: 8, bookName: "化学课本2" },   { id: 9, bookName: "化学课本1" },   { id: 10, bookName: "化学课本2" },   { id: 11, bookName: "物理课本1" },   { id: 12, bookName: "物理课本2" },   { id: 13, bookName: "物理课本3" },   { id: 14, bookName: "物理课本4" },   { id: 15, bookName: "生物课本1" },
]
​
​
/* 书架效果 */
const shelfRef = ref(null);//书架Ref
const shelfWidth = ref(1200);//书架宽度
​
// 构造页面数据
const bookData = computed(() => {               //页面渲染的数据   if (!shelfRef.value || !shelfWidth.value) {       return []   }
​   const counts = Math.floor(shelfWidth.value / 150);//每行可放置书本数   const rowCount = Math.ceil(apiData.length / counts);//总共有多少行   const rowArr = [];//书架二维数组
​   // 如果是最后一行且不满,添加占位元素,解决flex问题   for (let i = 0; i < rowCount; i++) {       const rowBooks = apiData.slice(i * counts, (i + 1) * counts);
​       if (i === rowCount - 1 && rowBooks.length < counts) {           const placeholders = Array(counts - rowBooks.length).fill().map((_, index) => ({               id: `placeholder-${index}`,               isPlaceholder: true,               bookName: '占位元素'           }));           rowArr.push([...rowBooks, ...placeholders]);       } else {           rowArr.push(rowBooks);       }
​   }
​   return rowArr;
})
​
// 更新屏幕宽度
const updateShelfWidth = () => {   shelfWidth.value = shelfRef.value.offsetWidth;
}
​
onMounted(() => {   updateShelfWidth();//页面加载后,更新下屏幕宽度   window.addEventListener('resize', updateShelfWidth);
})
​
onBeforeUnmount(() => {   window.removeEventListener('resize', updateShelfWidth);
})
</script>
​
<style>
.shelf {   width: 1200px;   height: auto;   border: 1px solid #ccc;   margin: 0 auto;   min-width: 1000px;
}
​
.shlef-row {   width: 100%;   margin: 0 0 20px 0;   display: flex;   border-bottom: 2px solid orange;
}
​
.shlef-row:last-child {   margin-bottom: 0;
}
​
.shlef-row .book-item:last-child {   margin-right: 0;
}
​
.book-item {   flex: 1;   box-sizing: border-box;   padding: 10px;   margin-right: 20px;   width: 130px;   height: 160px;   color: #fff;   background-color: skyblue;
}
</style>

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

http://www.jsqmd.com/news/362092/

相关文章:

  • 元气AI助手实战:3分钟教会你B站视频自动点赞(2026极简版)
  • 2026年知名的广东肠粉酱油,广东蒸鱼酱油,广东寿司酱油厂家采购决策指南 - 品牌鉴赏师
  • 超详细Agent Skills教程:零基础入门到精通,一篇就够了,赶紧收藏!
  • Agent开发教程(超详细)从零基础到精通,收藏这一篇就够了!
  • 解读闲置京东e卡如何快速提现到微信与支付宝 - 淘淘收小程序
  • 小程序计算机毕设之基于springboot+小程序的线上校招云校招助手小程序基于微信小程序的大学生就业管理系统设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • 盘点百联卡(OK卡)提现到微信或支付宝的小窍门 - 淘淘收小程序
  • 智能客服Agent全攻略(非常详细)从设计理念到工程落地,收藏这篇就够了!
  • 新手销售对接工厂:先搞懂这几个生产术语再开口
  • 计算机毕业设计之基于Spring Boot+Vue技术的医院处方管理系统设计与实现
  • SLC转运体服务哪家强?聚焦性价比,挖掘被低估的优秀厂家​ - 品牌推荐大师
  • 计算机类10大细分专业:计算机科学与技术、软件工程、数据科学、智能科学、网络安全、数字媒体、物联网、网络工程、信息安全、人工智能
  • 高分考生都来自哪里?2026主治医师考试通过率排名前十机构全解析,高效梳理备考逻辑 - 医考机构品牌测评专家
  • 计算机毕业设计之jsp基于JSP的校园宿舍电费缴纳系统
  • 张雪峰强烈推荐,这4个专业,未来10年最有“钱途”,毕业即躺赢!
  • libero PolarFire soc SPI-DirectC 实战 dp_G5M_read_idcode
  • 2026年最新发布!主治医师考试机构通过率TOP3排名,这份数据值得收藏 - 医考机构品牌测评专家
  • 上海开放大学社区管理作业答案
  • 在线拼接动图怎么弄?一键完成多动图无缝拼接不卡顿
  • 基于 Matlab GUI 的汽车零部件设计计算
  • 2026年全国薪酬设计咨询公司哪家强?聚焦落地性与定制化适配指南 落地性机构解析 - 深度智识库
  • GIF拼图工具推荐:多GIF拼接、自定义布局,制作GIF拼图零门槛
  • 计算机毕业设计之基于SpringBoot技术的在线团购系统设计与实现
  • 基于单片机远程防盗与恒温控制系统设
  • 终于有人把网络安全运维工程师需要学什么讲清了!
  • 北京聚信万通科技有限公司应Odette组织邀请加入OFTP2 专家组
  • 计算机毕业设计之springboot基于Web APP的音乐播放器设计
  • 专业动图静图拼接方法:简单高效,适配表情包、文章配图等全场景
  • 为什么运维都要转行网络安全?
  • 地 AI 模型不用愁!OpenWebUI+cpolar 解锁随时随地使用自由