这个问题比查找一行中有多少项要复杂一些。
最终,我们想知道活动元素的上方、下方、左侧和右侧是否有一个元素。这需要考虑到最后一行不完整的情况。例如,在下面的情况下,活动元素没有上、下或右项:
但是,为了确定活动项目的上方/下方/左侧/右侧是否有项目,我们需要知道一行中有多少项目。
查找每行的项目数
要获取我们需要的每行项目数:
-
itemWidth - 单个元素的outerWidth,包括border、padding 和margin
-
gridWidth - 网格的innerWidth,不包括border、padding 和margin
要使用纯 JavaScript 计算这两个值,我们可以使用:
const itemStyle = singleItem.currentStyle || window.getComputedStyle(active);
const itemWidth = singleItem.offsetWidth + parseFloat(itemStyle.marginLeft) + parseFloat(itemStyle.marginRight);
const gridStyle = grid.currentStyle || window.getComputedStyle(grid);
const gridWidth = grid.clientWidth - (parseFloat(gridStyle.paddingLeft) + parseFloat(gridStyle.paddingRight));
然后我们可以使用以下方法计算每行的元素数:
const numPerRow = Math.floor(gridWidth / itemWidth)
注意:这仅适用于统一大小的项目,并且仅当margin 以px 为单位定义时。
一种非常、非常、非常简单的方法
处理所有这些宽度、内边距、边距和边框确实令人困惑。有一个非常非常简单的解决方案。
我们只需要找到offsetTop属性大于第一个网格元素offsetTop的网格元素的索引。
const grid = Array.from(document.querySelector("#grid").children);
const baseOffset = grid[0].offsetTop;
const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
const numPerRow = (breakIndex === -1 ? grid.length : breakIndex);
最后的三元表示网格中只有一个项目和/或单行项目的情况。
const getNumPerRow = (selector) => {
const grid = Array.from(document.querySelector(selector).children);
const baseOffset = grid[0].offsetTop;
const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
return (breakIndex === -1 ? grid.length : breakIndex);
}
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 400px;
background-color: #ddd;
padding: 10px 0 0 10px;
margin-top: 5px;
resize: horizontal;
overflow: auto;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<button onclick="alert(getNumPerRow('#grid'))">Get Num Per Row</button>
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
但是上面或下面有项目吗?
要知道活动元素上方或下方是否有项目,我们需要知道 3 个参数:
totalItemsInGrid
activeIndex
numPerRow
例如,在以下结构中:
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
</div>
我们有 5 中的 totalItemsInGrid,activeIndex 有一个从零开始的索引 2(它是组中的第三个元素),假设 numPerRow 是 3。
我们现在可以确定活动项目的上方、下方、左侧或右侧是否有项目:
isTopRow = activeIndex <= numPerRow - 1
isBottomRow = activeIndex >= totalItemsInGid - numPerRow
isLeftColumn = activeIndex % numPerRow === 0
isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1
如果isTopRow 是true 我们不能向上移动,如果isBottomRow 是true 我们不能向下移动。如果isLeftColumn 是true 我们不能向左移动,如果isRightColumn 如果true 我们不能向右移动。
注意:isBottomRow 不仅会检查活动元素是否在底行,还会检查其下方是否有元素。在我们上面的示例中,活动元素是 不是 在底行,但在其下方没有项目。
一个工作示例
我已经把它变成了一个可以调整大小的完整示例 - 并使 #grid 元素可调整大小,以便可以在下面的 sn-p 中对其进行测试。
我创建了一个函数,navigateGrid,它接受三个参数:
-
gridSelector - 网格元素的 DOM 选择器
-
activeClass - 活动元素的类名
-
direction - up、down、left 或 right 之一
这可以用作'navigateGrid("#grid", "active", "up") 与您问题中的 HTML 结构。
该函数使用offset 方法计算行数,然后检查active 元素是否可以更改为上/下/左/右元素。
换句话说,该函数检查活动元素是否可以向上/向下和向左/向右移动。这意味着:
- 不能从最左边的列向左移动
- 不能从最右边的列开始
- 不能从顶行上升
- 不能从最下面一行往下走,或者如果下面的单元格是空的
const navigateGrid = (gridSelector, activeClass, direction) => {
const grid = document.querySelector(gridSelector);
const active = grid.querySelector(`.${activeClass}`);
const activeIndex = Array.from(grid.children).indexOf(active);
const gridChildren = Array.from(grid.children);
const gridNum = gridChildren.length;
const baseOffset = gridChildren[0].offsetTop;
const breakIndex = gridChildren.findIndex(item => item.offsetTop > baseOffset);
const numPerRow = (breakIndex === -1 ? gridNum : breakIndex);
const updateActiveItem = (active, next, activeClass) => {
active.classList.remove(activeClass);
next.classList.add(activeClass);
}
const isTopRow = activeIndex <= numPerRow - 1;
const isBottomRow = activeIndex >= gridNum - numPerRow;
const isLeftColumn = activeIndex % numPerRow === 0;
const isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1;
switch (direction) {
case "up":
if (!isTopRow)
updateActiveItem(active, gridChildren[activeIndex - numPerRow], activeClass);
break;
case "down":
if (!isBottomRow)
updateActiveItem(active, gridChildren[activeIndex + numPerRow], activeClass);
break;
case "left":
if (!isLeftColumn)
updateActiveItem(active, gridChildren[activeIndex - 1], activeClass);
break;
case "right":
if (!isRightColumn)
updateActiveItem(active, gridChildren[activeIndex + 1], activeClass);
break;
}
}
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 400px;
background-color: #ddd;
padding: 10px 0 0 10px;
margin-top: 5px;
resize: horizontal;
overflow: auto;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<button onClick='navigateGrid("#grid", "active", "up")'>Up</button>
<button onClick='navigateGrid("#grid", "active", "down")'>Down</button>
<button onClick='navigateGrid("#grid", "active", "left")'>Left</button>
<button onClick='navigateGrid("#grid", "active", "right")'>Right</button>
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>