模板
<ul
class="flex-1 flex flex-wrap gap-16 mt-5 overflow-y-scroll overflow-x-hidden ul-scrollbar"
id="snakeLayout"
>
<li
v-for="(item, zhangIndex) in [2, 3, 2, 4, 3, 9]"
class="group/li h-[244px] item relative rounded-xl flex-none flex flex-col bg-[#303a4ccc] border border-[#3e4b62]"
>
<!-- 标题 -->
<div class="flex-none bg-[#5e6a80] p-2 px-5 rounded-lg flex items-center justify-between">
<div class="text-lg line-clamp-1">第{{ zhangIndex + 1 }}章 语法分析器的构建</div>
<div>7个资源</div>
</div>
<!-- 子项 -->
<ul class="flex-1 overflow-hidden flex gap-16 justify-center p-5 relative z-10">
<li
v-for="(i, index) in item"
:key="index"
class="flex flex-col items-center odd:self-end even:self-start even:flex-col-reverse relative"
>
<div
class="w-8 h-8 rounded-full bg-[#72819C] relative cursor-pointer"
title="使用flex和Box布局中的"
>
<img
v-if="index % 2 === 0 && index !== item - 1"
src="@/assets/course_images/k-lj-1.png"
class="w-24 max-w-none absolute bottom-2 left-1"
alt=""
/>
<img
v-if="index % 2 === 1 && index !== item - 1"
src="@/assets/course_images/k-lj-2.png"
class="w-24 max-w-none absolute top-2 left-1"
alt=""
/>
<div class="w-8 h-8 rounded-full bg-[#72819C] absolute top-0 left-0"></div>
</div>
<div class="flex items-center gap-1 relative h-9">
<div class="flex-1 w-36 gap-2 absolute left-1/2 -translate-x-1/2 flex items-center">
<img
src="@/assets/course_images/k-dp-sp.svg"
v-if="index % 2 === 0 && index !== 8"
class="flex-none w-5"
alt=""
/>
<img
src="@/assets/course_images/k-dp-task.svg"
v-if="index % 2 === 1 && index !== 8"
class="flex-none w-5"
alt=""
/>
<span class="flex-1 line-clamp-1">使用flex和Box布局中的</span>
</div>
</div>
</li>
</ul>
<!-- 箭头 -->
<div class="absolute top-0 right-0 bottom-0 left-0 z-0">
<!-- left -->
<img
src="@/assets/course_images/k-arrow.png"
class="w-14 h-20 absolute rotate-180 -left-[3.56rem] top-1/2 -translate-y-1/2 hidden group-[.arrow-left]/li:block"
alt=""
/>
<!-- right -->
<img
src="@/assets/course_images/k-arrow.png"
class="w-14 h-20 absolute -right-[3.56rem] top-1/2 -translate-y-1/2 hidden group-[.arrow-right]/li:block"
alt=""
/>
<!-- bottom left -->
<img
src="@/assets/course_images/k-arrow.png"
class="w-14 h-20 absolute rotate-90 -bottom-[4.34rem] left-14 hidden group-[.arrow-bottom-left]/li:block"
alt=""
/>
<!-- bottom right -->
<img
src="@/assets/course_images/k-arrow.png"
class="w-14 h-20 absolute rotate-90 -bottom-[4.34rem] right-14 hidden group-[.arrow-bottom-right]/li:block"
alt=""
/>
</div>
</li>
</ul>
js
methods:{
applySnakeLayout() {
const items = Array.from(document.querySelectorAll('#snakeLayout .item'))
if (items.length === 0) return
// 获取容器宽度和gap
const container = document.getElementById('snakeLayout')
const containerWidth = container.offsetWidth
const gap = parseInt(getComputedStyle(container).gap) || 0
// 重置所有项目的样式
items.forEach((item) => {
item.style.order = ''
item.style.flex = ''
item.style.width = ''
// 移除之前添加的箭头类
item.classList.remove('arrow-right', 'arrow-left', 'arrow-down-right', 'arrow-down-left')
})
// 按照自然顺序分组行
let rows = []
let currentRow = []
let currentRowWidth = 0
// 按照DOM顺序处理项目
for (let i = 0; i < items.length; i++) {
const item = items[i]
const itemWidth = item.offsetWidth
// 计算添加此项目后的总宽度(包括gap)
const totalWidth = currentRow.length === 0 ? itemWidth : currentRowWidth + gap + itemWidth
// 如果能放下就添加到当前行,否则开始新行
if (totalWidth <= containerWidth || currentRow.length === 0) {
currentRow.push(item)
currentRowWidth = totalWidth
} else {
rows.push([...currentRow])
currentRow = [item]
currentRowWidth = itemWidth
}
}
// 添加最后一行
if (currentRow.length > 0) {
rows.push(currentRow)
}
// 为每行设置样式实现蛇形布局和撑满效果
let globalOrder = 1
rows.forEach((row, rowIndex) => {
// 计算当前行的总间隙
const totalGap = (row.length - 1) * gap
// 计算可用于项目的总宽度
const availableWidth = containerWidth - totalGap
// 计算当前行所有项目的自然宽度总和
const naturalWidthSum = row.reduce((sum, item) => sum + item.offsetWidth, 0)
if (rowIndex % 2 === 0) {
// 偶数行:从左到右 1 3 5 7 9...
row.forEach((item, colIndex) => {
const ratio = item.offsetWidth / naturalWidthSum
const flexWidth = availableWidth * ratio
item.style.width = `${flexWidth}px`
item.style.order = globalOrder++
// 添加.arrow-down-right类
if (rowIndex != rows.length - 1 || colIndex != row.length - 1) {
if (colIndex === row.length - 1) {
item.classList.add('arrow-bottom-right')
} else {
// 添加.arrow-right类
item.classList.add('arrow-right')
}
}
})
} else {
// 奇数行:从右到左 2 4 6 8 10...
for (let i = row.length - 1; i >= 0; i--) {
const item = row[i]
const colIndex = row.length - 1 - i
// 按比例分配宽度
const ratio = item.offsetWidth / naturalWidthSum
const flexWidth = availableWidth * ratio
item.style.width = `${flexWidth}px`
item.style.order = globalOrder++
if (rowIndex != rows.length - 1 || colIndex != row.length - 1) {
// 添加.arrow-left类
if (colIndex > 0) {
item.classList.add('arrow-left')
} else {
// 添加.arrow-down-left类
item.classList.add('arrow-bottom-left')
}
}
}
}
})
}
}