如何计算连续的 flexbox 项目的数量? [英] How to calculate the amount of flexbox items in a row?

查看:20
本文介绍了如何计算连续的 flexbox 项目的数量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

网格是使用 CSS flexbox 实现的.

本示例中的行数为 4,因为我出于演示目的固定了容器宽度.但是,实际上,它可以根据容器的宽度而改变(例如,如果用户调整窗口大小).尝试在

但是,为了确定活动项目的上方/下方/左侧/右侧是否有项目,我们需要知道一行中有多少项目.

求每行的项目数

要获得每行的项目数,我们需要:

  • itemWidth - 单个元素的outerWidth,包括borderpaddingmargin
  • gridWidth - 网格的innerWidth,不包括borderpaddingmargin

要使用纯 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)

注意:这仅适用于统一大小的项目,并且仅当 marginpx 单位定义时才有效.

一种非常简单的方法

处理所有这些宽度、填充、边距和边框真的很令人困惑.有一个非常非常简单的解决方案.

我们只需要找到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);返回 (breakIndex === -1 ? grid.length : breakIndex);}

.grid {显示:弹性;flex-wrap: 包裹;对齐内容:flex-start;宽度:400px;背景颜色:#ddd;填充:10px 0 0 10px;边距顶部:5px;调整大小:水平;溢出:自动;}.物品 {宽度:50px;高度:50px;背景颜色:红色;边距:0 10px 10px 0;}.active.item {轮廓:5px纯黑色;}

<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>

但是上面或下面有一个项目吗?

要知道活动元素上方或下方是否有项目,我们需要知道 3 个参数:

  • totalItemsInGrid
  • activeIndex
  • numPerRow

例如,在以下结构中:

<div class="item"></div><div class="item"></div><div class="item active"></div><div class="item"></div><div class="item"></div>

我们有一个 5totalItemsInGridactiveIndex 有一个 2 从零开始的索引(它是组中的第三个元素),假设 numPerRow 是 3.

我们现在可以通过以下方式确定活动项目的上方、下方、左侧或右侧是否有项目:

  • isTopRow = activeIndex <= numPerRow - 1
  • isBottomRow = activeIndex >= totalItemsInGid - numPerRow
  • isLeftColumn = activeIndex % numPerRow === 0
  • isRightColumn = activeIndex % numPerRow === numPerRow - 1 ||activeIndex === gridNum - 1

如果 isTopRowtrue 我们不能向上移动,如果 isBottomRowtrue 我们不能向下移动.如果 isLeftColumntrue 我们不能向左移动,如果 isRightColumn 如果 true 我们不能向右移动.

注意:isBottomRow 不仅检查活动元素是否在底行,还会检查其下方是否有元素.在我们上面的示例中,活动元素not 位于底行,但其下方没有项目.

一个工作示例

我已经将它变成了一个可以调整大小的完整示例 - 并使 #grid 元素可以调整大小,以便可以在下面的代码片段中对其进行测试.

我创建了一个函数,navigateGrid,它接受三个参数:

  • gridSelector - 网格元素的 DOM 选择器
  • activeClass - 活动元素的类名
  • direction - updownleftright 之一

这可以用作 '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;开关(方向){案例向上":如果 (!isTopRow)updateActiveItem(active, gridChildren[activeIndex - numPerRow], activeClass);休息;案例下降":如果 (!isBottomRow)updateActiveItem(active, gridChildren[activeIndex + numPerRow], activeClass);休息;案例左":如果 (!isLeftColumn)updateActiveItem(active, gridChildren[activeIndex - 1], activeClass);休息;案例正确":如果 (!isRightColumn)updateActiveItem(active, gridChildren[activeIndex + 1], activeClass);休息;}}

.grid {显示:弹性;flex-wrap: 包裹;对齐内容:flex-start;宽度:400px;背景颜色:#ddd;填充:10px 0 0 10px;边距顶部:5px;调整大小:水平;溢出:自动;}.物品 {宽度:50px;高度:50px;背景颜色:红色;边距:0 10px 10px 0;}.active.item {轮廓:5px纯黑色;}

<button onClick='navigateGrid("#grid", "active", "down")'>Down</button><button onClick='navigateGrid("#grid", "active", "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>

A grid is implemented using the CSS flexbox. Example:

The number of rows in this example is 4 because I fixed the container width for demo purposes. But, in reality, it can change based on container's width (e.g. if the user resizes the window). Try to resize the Output window in this example to get a feeling.

There is always one active item, marked with the black border.

Using JavaScript, I allow users to navigate to the previous/next item using the left/right arrow. In my implementation, I just decrease/increase the index of the active item by 1.

Now, I'd like to allow users to navigate up/down as well. For that, I just need to decrease/increase the index of the active item by <amount of items in a row>. But, how do I calculate this number given that it is dependent on container's width? Is there a better way to implement the up/down functionality?

.grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  width: 250px;
  height: 200px;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

.item {
  width: 50px;
  height: 50px;
  background-color: red;
  margin: 0 10px 10px 0;
}

.active.item {
  outline: 5px solid black;
}

<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>

解决方案

The question is slightly more complex than finding how many items are in a row.

Ultimately, we want to know if there's an element above, below, left, and right of the active element. And this needs to account for cases where the bottom row is incomplete. For example, in the case below, the active element has no item above, below, or right:

But, in order to determine if there's an item above/below/left/right of the active item, we need to know how many items are in a row.

Find the number of items per row

To get the number of items per row we need:

  • itemWidth - the outerWidth of a single element including border, padding and margin
  • gridWidth - the innerWidth of the grid, excluding border, padding and margin

To calculate these two values with plain JavaScript we can use:

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));

Then we can calculate the number of elements per row using:

const numPerRow = Math.floor(gridWidth / itemWidth)

Note: this will only work for uniform-sized items, and only if the margin is defined in px units.

A Much, Much, Much Simpler Approach

Dealing with all these widths, and paddings, margins, and borders is really confusing. There's a much, much, much simpler solution.

We only need to find the index of the grid element who's offsetTop property is greater than the first grid element's 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);

The ternary at the end accounts for the cases when there's only a single item in the grid, and/or a single row of items.

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>

But is there an item above or below?

To know if there's an item above or below the active element we need to know 3 parameters:

  • totalItemsInGrid
  • activeIndex
  • numPerRow

For example, in the following structure:

<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>

we have a totalItemsInGrid of 5, the activeIndex has a zero-based index of 2 (it's the 3rd element in the group), and let's say the numPerRow is 3.

We can now determine if there's an item above, below, left, or right of the active item with:

  • isTopRow = activeIndex <= numPerRow - 1
  • isBottomRow = activeIndex >= totalItemsInGid - numPerRow
  • isLeftColumn = activeIndex % numPerRow === 0
  • isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1

If isTopRow is true we cannot move up, and if isBottomRow is true we cannot move down. If isLeftColumn is true we cannot move left, and if isRightColumn if true we cannot move right.

Note: isBottomRow doesn't only check if the active element is on the bottom row, but also checks if there's an element beneath it. In our example above, the active element is not on the bottom row, but doesn't have an item beneath it.

A Working Example

I've worked this into a full example that works with resizing - and made the #grid element resizable so it can be tested in the snippet below.

I've created a function, navigateGrid that accepts three parameters:

  • gridSelector - a DOM selector for the grid element
  • activeClass - the class name of the active element
  • direction - one of up, down, left, or right

This can be used as 'navigateGrid("#grid", "active", "up") with the HTML structure from your question.

The function calculates the number of rows using the offset method, then does the checks to see if the active element can be changed to the up/down/left/right element.

In other words, the function checks if the active element can be moved up/down and left/right. This means:

  • can't go left from the left-most column
  • can't go right from the right-most column
  • can't go up from the top row
  • can't go down from the bottom row, or if the cell below is empty

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>

这篇关于如何计算连续的 flexbox 项目的数量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆