如何计算连续的flexbox项目数量?

时间:2018-03-01 06:18:11

标签: javascript html css css3 flexbox

使用CSS flexbox实现网格。 Example:

enter image description here

此示例中的行数为4,因为我修复了容器宽度以用于演示目的。但是,实际上,它可以根据容器的宽度进行更改(例如,如果用户调整窗口大小)。尝试调整this example中的“输出”窗口的大小以获得感觉。

总有一个活动项目,标有黑色边框。

使用JavaScript,我允许用户使用左/右箭头导航到上一个/下一个项目。在我的实现中,我只是将活动项的索引减少/增加1。

现在,我想让用户也可以向上/向下导航。为此,我只需要将活动项的索引减少/增加<amount of items in a row>。但是,如果它取决于容器的宽度,我该如何计算这个数?有没有更好的方法来实现上/下功能?

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

10 个答案:

答案 0 :(得分:22)

(为了获得最佳体验,请在整页上更好地运行交互式代码段)

计算每行元素数量

你需要获得一个元素的宽度它的边距 (如果它们也被设置,最终边界)然后你需要得到容器的内部宽度< strong>没有填充。拥有这两个值后,您可以进行简单的除法以获得每行的元素数。

不要忘记考虑只有一行的情况,因此您需要获得元素总数与从该部门获得的数字之间的最小值。

&#13;
&#13;
//total number of element
var n_t = document.querySelectorAll('.item').length;
//width of an element
var w = parseInt(document.querySelector('.item').offsetWidth);
//full width of element with margin
var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
//width of container
var w_c = parseInt(document.querySelector('.grid').offsetWidth);
//padding of container
var c = document.querySelector('.grid').currentStyle || window.getComputedStyle(document.querySelector('.grid'));
var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
//nb element per row
var nb = Math.min(parseInt((w_c - p_c) / w),n_t);
console.log(nb);


window.addEventListener('resize', function(event){
   //only the width of container will change
   w_c = parseInt(document.querySelector('.grid').offsetWidth);
   nb = Math.min(parseInt((w_c - p_c) / w),n_t);
   console.log(nb);
});
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  resize:horizontal;
  align-content: flex-start;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

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

.active.item {
  outline: 5px solid black;
}
&#13;
<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 class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
&#13;
&#13;
&#13;

这是一个jQuery版本的相同逻辑,代码较少:

&#13;
&#13;
//total number of element
var n_t = $('.item').length;
//full width of element with margin
var w = $('.item').outerWidth(true);
//width of container without padding
var w_c = $('.grid').width();
//nb element per row
var nb = Math.min(parseInt(w_c / w),n_t);
console.log(nb);

window.addEventListener('resize', function(event){
   //only the width of container will change
   w_c = $('.grid').width();
   nb = Math.min(parseInt(w_c / w),n_t);
   console.log(nb);
});
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  resize:horizontal;
  align-content: flex-start;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

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

.active.item {
  outline: 5px solid black;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<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 class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
&#13;
&#13;
&#13;

以下是交互式网格的演示:

&#13;
&#13;
var all = document.querySelectorAll('.item');
var n_t = all.length;
var current = 0;
all[current].classList.add('active');

var w = parseInt(document.querySelector('.item').offsetWidth);
var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
var w_c = parseInt(document.querySelector('.grid').offsetWidth);
var c = document.querySelector('.grid').currentStyle || window.getComputedStyle(document.querySelector('.grid'));
var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
var nb = Math.min(parseInt((w_c - p_c) / w),n_t);

window.addEventListener('resize', function(e){
   w_c = parseInt(document.querySelector('.grid').offsetWidth);
   nb = Math.min(parseInt((w_c - p_c) / w),n_t);
});

document.addEventListener('keydown',function (e) {
    e = e || window.event;
    if (e.keyCode == '38') {
        if(current - nb>=0) {
          all[current].classList.remove('active');
          current-=nb;
          all[current].classList.add('active');
       }
    }
    else if (e.keyCode == '40') {
        if(current + nb<n_t) {
          all[current].classList.remove('active');
          current+=nb;
          all[current].classList.add('active');
       }
    }
    else if (e.keyCode == '37') {
       if(current>0) {
          all[current].classList.remove('active');
          current--;
          all[current].classList.add('active');
       }
    }
    else if (e.keyCode == '39') {
       if(current<n_t-1) {
          all[current].classList.remove('active');
          current++;
          all[current].classList.add('active');
       }
          
    }
});
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  resize:horizontal;
  align-content: flex-start;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

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

.active.item {
  outline: 5px solid black;
}
&#13;
<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"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
&#13;
&#13;
&#13;

另一个想法

我们还可以考虑另一种在网格内导航的方法,而不需要每行的元素数量。我们的想法是依靠函数elementFromPoint(x,y)

逻辑如下:我们在一个活跃的元素中,我们有(x,y)的位置。通过按键我们将增加/减少这些值,我们使用上面的函数来使用新的(x,y)获取新元素。我们测试是否获得有效元素,以及此元素是否为项目(包含item类)。在这种情况下,我们从前一个中移除active,然后将其添加到新的。

以下是我只考虑内部导航的示例。当我们到达容器的左/右边界时,我们将无法到达上一行/下一行:

&#13;
&#13;
var a = document.querySelector('.item');
a.classList.add('active');

var off = a.getBoundingClientRect();
/* I get the center position to avoid any potential issue with boundaries*/
var y = off.top + 40; 
var x = off.left + 40;

document.addEventListener('keydown', function(e) {
  e = e || window.event;
  if (e.keyCode == '38') {
    var elem = document.elementFromPoint(x, y - 90 /* width + both margin*/);
    if (elem &&
      elem.classList.contains('item')) {
      document.querySelector('.active').classList.remove('active');
      elem.classList.add('active');
      y -= 90;
    }
  } else if (e.keyCode == '40') {
    var elem = document.elementFromPoint(x, y + 90);
    if (elem &&
      elem.classList.contains('item')) {
      document.querySelector('.active').classList.remove('active');
      elem.classList.add('active');
      y += 90;
    }
  } else if (e.keyCode == '37') {
    var elem = document.elementFromPoint(x - 90, y);
    if (elem &&
      elem.classList.contains('item')) {
      document.querySelector('.active').classList.remove('active');
      elem.classList.add('active');
      x -= 90;
    }
  } else if (e.keyCode == '39') {
    var elem = document.elementFromPoint(x + 90, y);
    if (elem &&
      elem.classList.contains('item')) {
      document.querySelector('.active').classList.remove('active');
      elem.classList.add('active');
      x += 90;
    }
  }
});

window.addEventListener('resize', function(e) {
  var off = document.querySelector('.active').getBoundingClientRect();
  y = off.top + 40;
  x = off.left + 40;
});
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  resize: horizontal;
  align-content: flex-start;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

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

.active.item {
  outline: 5px solid black;
}
&#13;
<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"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
&#13;
&#13;
&#13;

正如您在此方法中所注意到的,我们不需要有关容器,屏幕大小,元素数量等的任何信息。唯一需要的信息是单个项目的维度。我们还需要一个小代码来纠正窗口大小调整时活动元素的位置。

加成

如果您想要一个可视活动元素而不需要添加类或使用JS获取它,那么这是另一个花哨的想法。我们的想法是在容器上使用background来在活动元素后面创建一个黑盒子。

顺便说一句,这种方法有两个缺点:

  1. 如果它没有充满元素,不容易处理最后一行,因为我们可能没有任何黑盒子
  2. 我们必须考虑每行最后一个元素后留下的空格,以避免黑盒子有一个奇怪的位置。
  3. 这是一个带有固定高度/宽度容器的简化代码:

    &#13;
    &#13;
    var grid = document.querySelector('.grid');
    
    document.addEventListener('keydown', function(e) {
      e = e || window.event;
      if (e.keyCode == '38') {
        var y = parseInt(grid.style.backgroundPositionY);
        y= (y-90 + 270)%270;
        grid.style.backgroundPositionY=y+"px";
      } else if (e.keyCode == '40') {
        var y = parseInt(grid.style.backgroundPositionY);
        y= (y+90)%270;
        grid.style.backgroundPositionY=y+"px";
      } else if (e.keyCode == '37') {
        var x = parseInt(grid.style.backgroundPositionX);
        x= (x-90 + 270)%270;
        grid.style.backgroundPositionX=x+"px";
      } else if (e.keyCode == '39') {
        var x = parseInt(grid.style.backgroundPositionX);
        x= (x+90)%270;
        grid.style.backgroundPositionX=x+"px";
      }
    });
    &#13;
    .grid {
      display: flex;
      flex-wrap: wrap;
      width:270px;
      resize: horizontal;
      align-content: flex-start;
      background-color: #ddd;
      padding: 10px 0 0 10px;
      background-image:linear-gradient(#000,#000);
      background-size:90px 90px;
      background-repeat:no-repeat;
    }
    
    .item {
      width: 80px;
      height: 80px;
      background-color: red;
      margin: 0 10px 10px 0;
    }
    &#13;
    <div id="grid" class="grid" style="background-position:5px 5px;">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>
    &#13;
    &#13;
    &#13;

    正如我们所看到的,代码非常简单,因此它适用于几乎所有值都已知并且已修复的情况。

答案 1 :(得分:8)

根据我的知识,上下移动的唯一方法是减少不必要的并发症,即每行计算一个盒子并更改索引。唯一的问题是你需要在窗口加载和调整大小事件上计算boxcount。

var boxPerRow=0;
function calculateBoxPerRow(){}
window.onload = calculateBoxPerRow; 
window.onresize = calculateBoxPerRow;

现在,如果你想要一个非常简单的方法来获得连续的行数而不管容器和盒子的大小忘记边距和填充,您可以检查与第一个框对齐的框数比较offsetTop属性

  

HTMLElement.offsetTop只读属性返回当前元素相对于offsetParent节点顶部的距离。 [来源:developer.mozilla.orgl]

您可以像下面这样实现它:

function calculateBoxPerRow(){
    var boxes = document.querySelectorAll('.item');
    if (boxes.length > 1) {
‎       var i = 0, total = boxes.length, firstOffset = boxes[0].offsetTop;
‎       while (++i < total && boxes[i].offsetTop == firstOffset);
‎       boxPerRow = i;
‎   }
}

完整的工作示例:

(function() {
  var boxes = document.querySelectorAll('.item');
  var boxPerRow = 0, currentBoxIndex = 0;

  function calculateBoxPerRow() {
    if (boxes.length > 1) {
      var i = 0,
        total = boxes.length,
        firstOffset = boxes[0].offsetTop;
      while (++i < total && boxes[i].offsetTop == firstOffset);
      boxPerRow = i;
    }
  }
  window.onload = calculateBoxPerRow;
  window.onresize = calculateBoxPerRow;

  function focusBox(index) {
    if (index >= 0 && index < boxes.length) {
      if (currentBoxIndex > -1) boxes[currentBoxIndex].classList.remove('active');
      boxes[index].classList.add('active');
      currentBoxIndex = index;
    }
  }
  document.body.addEventListener("keyup", function(event) {
    switch (event.keyCode) {
      case 37:
        focusBox(currentBoxIndex - 1);
        break;
      case 39:
        focusBox(currentBoxIndex + 1);
        break;
      case 38:
        focusBox(currentBoxIndex - boxPerRow);
        break;
      case 40:
        focusBox(currentBoxIndex + boxPerRow);
        break;
    }
  });
})();
.grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  width: 50%;
  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>[You need to click on this page so that it can recieve the arrow keys]</div>
<div id="grid" class="grid">
  <div class="item active"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

答案 2 :(得分:2)

为了支持向上,向下,向左和向右移动,你不需要知道连续多少个盒子,你只需要计算上面,下面,左边是否有一个盒子,或者活动框右侧。

您已经注意到左右移动很简单 - 只需检查活动框是否有previousSiblingElementnextSiblingElement。对于向上和向下,您可以使用当前活动框作为锚点,并将其与其他框getBoundingClientRect()进行比较,DOM method返回相对于其他元素的geomoetry浏览器视口。

当试图向上移动时,从锚点开始并向下计数向下的项目。向下移动时,从锚点开始计数直到项目数量结束。这是因为当向上移动时,我们只关心活动盒子之前的盒子,而当它下降时我们只关心它之后的盒子。我们需要寻找的是一个具有相同左侧位置且顶部位置较高或较低的盒子。

下面是一个示例,它监听window上的keydown事件,并根据按下的箭头键移动活动状态。绝对可以做得更干,但是我把四个案例分开了,所以你可以看到每个案例中的确切逻辑。您可以按住箭头键使框连续移动,您可以看到它非常高效。我已在此处使用我的解决方案更新了您的JSBin:http://jsbin.com/senigudoqu/1/edit?html,css,js,output

&#13;
&#13;
const items = document.querySelectorAll('.item');

let activeItem = document.querySelector('.item.active');

function updateActiveItem(event) {
  let index;
  let rect1;
  let rect2;

  switch (event.key) {
    case 'ArrowDown':
      index = Array.prototype.indexOf.call(items, activeItem);
      rect1 = activeItem.getBoundingClientRect();

      for (let i = index; i < items.length; i++) {
        rect2 = items[i].getBoundingClientRect();

        if (rect1.x === rect2.x && rect1.y < rect2.y) {
          items[i].classList.add('active');
          activeItem.classList.remove('active');
          activeItem = items[i];
          return;
        }
      }
      break;

    case 'ArrowUp':
      index = Array.prototype.indexOf.call(items, activeItem);
      rect1 = activeItem.getBoundingClientRect();

      for (let i = index; i >= 0; i--) {
        rect2 = items[i].getBoundingClientRect();

        if (rect1.x === rect2.x && rect1.y > rect2.y) {
          items[i].classList.add('active');
          activeItem.classList.remove('active');
          activeItem = items[i];
          return;
        }
      }
      break;

    case 'ArrowLeft':
      let prev = activeItem.previousElementSibling;

      if (prev) {
        prev.classList.add('active');
        activeItem.classList.remove('active');
        activeItem = prev;
      }
      break;

    case 'ArrowRight':
      let next = activeItem.nextElementSibling;

      if (next) {
        next.classList.add('active');
        activeItem.classList.remove('active');
        activeItem = next;
      }
      break;

    default:
      return;
  }
}

window.addEventListener('keydown', updateActiveItem);
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  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;
}
&#13;
  <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 class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
  </div>
&#13;
&#13;
&#13;

答案 3 :(得分:1)

虽然您可以计算出您要查找的元素,但我建议您搜索下面的元素。这样做的好处是,如果你的元素没有相同的宽度,它甚至可以工作。

因此,让我们考虑下面元素的属性。基本上它是第一个具有更高offsetTop和相同offsetLeft的元素。你可以做这样的事情来找到ontop上的元素:

const active = document.querySelector('.item.active');
const all = [...document.querySelectorAll('.item')]
const below = all
  .filter(c => c.offsetTop > active.offsetTop)
  .find(c => c.offsetLeft >= active.offsetLeft)
const ontop = [...all].reverse()
  .filter(c => c.offsetTop < active.offsetTop)
  .find(c => c.offsetLeft >= active.offsetLeft)

答案 4 :(得分:1)

此示例假设移动在边界处结束。此外,如果从第二行向下移动到最后一行,但最后一行中的列数较少,则会转移到最后一行的最后一列。

此解决方案跟踪行/列,并使用网格对象来跟踪元素的位置。调整页面大小时,将在网格对象中更新位置。

(您可以在全屏模式下查看包装更新)

&#13;
&#13;
var items = document.querySelectorAll(".item");
var grid = {}; // keys: row, values: index of div in items variable
var row, col, numRows;

// called only onload and onresize
function populateGrid() {
    grid = {};
    var prevTop = -99;
    var row = -1;

    for(idx in items) {
        if(isNaN(idx)) continue;

        if(items[idx].offsetTop !== prevTop) {
          prevTop = items[idx].offsetTop;
          row++;
          grid[row] = [];
        }
        grid[row].push(idx);
    }

    setActiveRowAndCol();
    numRows = Object.keys(grid).length
}

// changes active state from one element to another
function updateActiveState(oldElem, newElem) {
    oldElem.classList.remove('active');
    newElem.classList.add('active');
}

// only called from populateGrid to get new row/col of active element (in case of wrap)
function setActiveRowAndCol() {
    var activeIdx = -1;
    for(var idx in items) {
        if(items[idx].className == "item active")
            activeIdx = idx;
    }

    for(var key in grid) {
        var gridIdx = grid[key].indexOf(activeIdx);
        if(gridIdx > -1) {
          row = key;
          col = gridIdx;
        }
    }
}

function moveUp() {
    if(0 < row) {
        var oldElem = items[grid[row][col]];
        row--;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}

function moveDown() {
    if(row < numRows - 1) {
        var oldElem = items[grid[row][col]];
        row++;
        var rowLength = grid[row].length
        var newElem;

        if(rowLength-1 < col) {
            newElem = items[grid[row][rowLength-1]]
            col = rowLength-1;
        } else {
            newElem = items[grid[row][col]];
        }
        updateActiveState(oldElem, newElem);
    }
}

function moveLeft() {
    if(0 < col) {
        var oldElem = items[grid[row][col]];
        col--;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}

function moveRight() {
    if(col < grid[row].length - 1) {
        var oldElem = items[grid[row][col]];
        col++;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}



document.onload = populateGrid();
window.addEventListener("resize", populateGrid);

document.addEventListener('keydown', function(e) {
    e = e || window.event;
    if (e.keyCode == '38') {
        moveUp();
    } else if (e.keyCode == '40') {
        moveDown();
    } else if (e.keyCode == '37') {
        moveLeft();
    } else if (e.keyCode == '39') {
        moveRight();
    }
});
&#13;
.grid {
  display: flex;
  flex-wrap: wrap;
  resize: horizontal;
  align-content: flex-start;
  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;
}
&#13;
<div id="grid" class="grid">
  <div class="item active"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
&#13;
&#13;
&#13;

答案 5 :(得分:0)

我知道这不是OP所要求的,但我想展示一种可能的选择(取决于用例)。

除了使用CSS flexbox之外,还有更新的CSS网格实际上具有列和行。因此,通过将结构转换为网格并使用一些JS来监听被按下的键按钮,可以移动活动项(参见下面的不完整的工作示例)。

&#13;
&#13;
var x = 1, y = 1;
document.addEventListener('keydown', function(event) {
    const key = event.key; 
    // "ArrowRight", "ArrowLeft", "ArrowUp", or "ArrowDown"
    console.log(key);
    
    if (key == "ArrowRight") {
      x++;
    }
    if (key == "ArrowLeft") {
      x--;
      if (x < 1) {
        x = 1;
      }
    }
    if (key == "ArrowUp") {
      y--;
      if (y < 1) {
        y = 1;
      }
    }
    if (key == "ArrowDown") {
      y++;
    }
    document.querySelector('.active').style.gridColumnStart = x;
    document.querySelector('.active').style.gridRowStart = y;
});
&#13;
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill,50px);
  grid-template-rows: auto;
  grid-gap: 10px;
  width: 250px;
  height: 200px;
  background-color: #ddd;
  padding: 10px;
}

.item {
  width: 50px;
  height: 50px;
  background-color: red;
  margin: 0 10px 10px 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.active {
  outline: 5px solid black;
  grid-column-start: 1;
  grid-column-end: span 1;
  grid-row-start: 1;
  grid-row-end: span 1;
}
&#13;
<div id="grid" class="grid">
  <div class="item active">A1</div>
  <div class="item">A2</div>
  <div class="item">A3</div>
  <div class="item">A4</div>
  <div class="item">B1</div>
  <div class="item">B2</div>
  <div class="item">B3</div>
  <div class="item">B4</div>
  <div class="item">C1</div>
  <div class="item">C2</div>
</div>
&#13;
&#13;
&#13;

然而,如上所述,该解决方案存在缺陷。一次,活动项目实际上是一个网格项目,并沿着网格移动,其他元素在其周围流动。其次,类似于flexbox模型,目前没有CSS选择器根据其网格位置来定位项目。

但是,由于我们无论如何都在使用javascript,您可以循环遍历所有网格项并获取CSS Grid属性。如果它们与当前坐标匹配,则表示您有目标元素。遗憾的是,这只有放置每个元素才有效,使用grid-column-start: auto元素并不起作用。即使window.getComputedStyle()也只会返回auto;

答案 6 :(得分:0)

你可以使用Array.prototype.filter()来做得非常整齐。 要获取连续项目的数量,请使用此功能。 传入要使用的CSS选择器(在本例中为.item)。 获得行大小后,箭头导航很容易。

function getRowSize( cssSelector ) {

    var firstTop = document.querySelector( cssSelector ).offsetTop;

    // Sets rowArray to be an array of the nodes (divs) in the 1st row.
    var rowArray = Array.prototype.filter.call(document.querySelectorAll( cssSelector ), function(element){
        if( element.offsetTop == firstTop ) return element;
    });

    // Return the amount of items in a row.
    return rowArray.length;
}

<强>实施例

CodePen演示:https://codepen.io/gtlitc/pen/EExXQE

显示行大小和移动金额的交互式演示。 http://www.smallblue.net/demo/49043684/

<强>解释

首先,该函数将变量firstTop设置为第一个节点的offsetTop

接下来,该函数在第一行中构建一个节点的数组rowArray(如果可以向上和向下导航,则第一行将始终是一个全长行。)

这是通过从Array Prototype调用(借用)过滤器函数来完成的。我们不能简单地在QSA(查询选择器all)返回的节点列表上调用过滤器函数,因为浏览器返回节点列表而不是数组,节点列表不是正确的数组。

if语句然后只过滤所有节点,并仅返回与第一个节点具有相同offsetTop的节点。即第一行中的所有节点。

我们现在有一个数组,我们可以从中确定行的长度。

我省略了DOM遍历的实现,因为这很简单,使用纯Javascript或Jquery等,并且不是OP问题的一部分。我只会注意到,在移动之前测试你想移动的元素是否存在是非常重要的。

此功能适用于任何布局技术。 Flexbox,float,CSS网格,无论未来如何。

<强>参考

Why does document.querySelectorAll return a StaticNodeList rather than a real Array?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

答案 7 :(得分:0)

offsetTop是确定元素y位置的常用方法。

如果两个相邻的兄弟元素具有相同的y位置,我们可以安全地假设它们在视觉上位于同一行(因为所有元素具有相同的高度)。

因此,我们可以通过逐个比较它们的y位来开始计算一行中元素的数量。我们一旦用完元素就会停止计数,或者我们遇到一个具有不同y位置的相邻兄弟。

function getCountOfItemsInRow() {
    let grid = document.getElementById('grid').children; //assumes #grid exists in dom
    let n = 0; // Zero items when grid is empty

    // If the grid has items, we assume the 0th element is in the first row, and begin counting at 1
    if (grid.length > 0) {
        n = 1; 

        // While the nth item has the same height as the previous item, count it as an item in the row. 
        while (grid[n] && grid[n].offsetTop === grid[n - 1].offsetTop) {
            n++;
        }
    }

    return n;
}

答案 8 :(得分:0)

此示例假设移动在边界处结束。此外,如果从第二行向下移动到最后一行,但最后一行中的列数较少,则会转移到最后一行的最后一列。

此解决方案跟踪行/列,并使用网格对象来跟踪元素的位置。

var items = document.querySelectorAll(".item");
var grid = {}; // keys: row, values: index of div in items variable
var row, col, numRows;

// called only onload and onresize
function populateGrid() {
    grid = {};
    var prevTop = -99;
    var row = -1;

    for(idx in items) {
        if(isNaN(idx)) continue;

        if(items[idx].offsetTop !== prevTop) {
          prevTop = items[idx].offsetTop;
          row++;
          grid[row] = [];
        }
        grid[row].push(idx);
    }

    setActiveRowAndCol();
    numRows = Object.keys(grid).length
}

// changes active state from one element to another
function updateActiveState(oldElem, newElem) {
    oldElem.classList.remove('active');
    newElem.classList.add('active');
}

// only called from populateGrid to get new row/col of active element (in case of wrap)
function setActiveRowAndCol() {
    var activeIdx = -1;
    for(var idx in items) {
        if(items[idx].className == "item active")
            activeIdx = idx;
    }

    for(var key in grid) {
        var gridIdx = grid[key].indexOf(activeIdx);
        if(gridIdx > -1) {
          row = key;
          col = gridIdx;
        }
    }
}

function moveUp() {
    if(0 < row) {
        var oldElem = items[grid[row][col]];
        row--;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}

function moveDown() {
    if(row < numRows - 1) {
        var oldElem = items[grid[row][col]];
        row++;
        var rowLength = grid[row].length
        var newElem;

        if(rowLength-1 < col) {
            newElem = items[grid[row][rowLength-1]]
            col = rowLength-1;
        } else {
            newElem = items[grid[row][col]];
        }
        updateActiveState(oldElem, newElem);
    }
}

function moveLeft() {
    if(0 < col) {
        var oldElem = items[grid[row][col]];
        col--;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}

function moveRight() {
    if(col < grid[row].length - 1) {
        var oldElem = items[grid[row][col]];
        col++;
        var newElem = items[grid[row][col]];
        updateActiveState(oldElem, newElem);
    }
}



document.onload = populateGrid();
window.addEventListener("resize", populateGrid);

document.addEventListener('keydown', function(e) {
    e = e || window.event;
    if (e.keyCode == '38') {
        moveUp();
    } else if (e.keyCode == '40') {
        moveDown();
    } else if (e.keyCode == '37') {
        moveLeft();
    } else if (e.keyCode == '39') {
        moveRight();
    }
});
.grid {
  display: flex;
  flex-wrap: wrap;
  resize: horizontal;
  align-content: flex-start;
  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 active"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

答案 9 :(得分:0)

如果您正在使用Jquery并确信您的网格对象是垂直对齐的,那么这可以解决问题..

我没有测试它,但它应该工作(通过计算列)

function countColumns(){
   var objects = $(".grid-object"); // choose a unique class name here
   var columns = []

   for(var i=0;i<objects.length;i++){
      var pos = $(objects[i]).position().left
      if(columns.indexOf(pos) < 1) columns.push(pos);
   }
   return columns.length
}