Как рассчитать количество элементов flexbox в строке?

Сетка реализована с использованием Flexbox. Пример:

enter image description here

Количество строк в этом примере равно 4, потому что я установил ширину контейнера для демонстрационных целей. Но на самом деле он может меняться в зависимости от ширины контейнера (например, если пользователь изменяет размер окна). Попробуйте изменить размер окна вывода в этом примере, чтобы получить представление.

Всегда есть один активный элемент, отмеченный черной рамкой.

Используя 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>

Ответ 1

(Для оптимального опыта лучше всего использовать интерактивные фрагменты на полной странице)

Вычисление количества элементов в строке

Вам нужно получить ширину элемента с его границей (в конце концов, если они установлены), тогда вам нужно получить внутреннюю ширину контейнера без заполнения. Имея эти 2 значения, вы делаете простое разделение, чтобы получить число элементов в строке.

Не забудьте рассмотреть случай, когда у вас есть только одна строка, поэтому вам нужно получить минимальное значение между общим количеством элементов и числом, которое вы получаете от подразделения.

//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);
});
.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;
}
<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>

Ответ 2

Вопрос немного более сложный, чем поиск количества элементов в строке.

В конечном счете, мы хотим знать, есть ли элемент выше, ниже, слева и справа от активного элемента. И это должно учитывать случаи, когда нижняя строка является неполной. Например, в приведенном ниже примере активный элемент не имеет элемента выше, ниже или справа:

enter image description here

Но, чтобы определить, есть ли элемент выше/ниже/слева/справа от активного элемента, нам нужно знать, сколько элементов находится в строке.

Найдите количество элементов в строке

Чтобы получить количество элементов в строке, нам нужно:

  • 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

Единственный способ передвижения вверх и вниз, который возникает из-за менее нежелательного усложнения моих знаний, - это количество ящиков в строке и изменение индексов. Единственная проблема заключается в том, что вам нужно вычислить бокс-сечение как при загрузке окна, так и при изменении размера.

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>

Ответ 4

Чтобы поддерживать перемещение вверх, вниз, влево и вправо, вам не нужно знать, сколько ящиков есть в строке, вам просто нужно вычислить, есть ли над ним, внизу, слева или справа от активной коробки,

Перемещение влево и вправо прост, как вы заметили, - просто проверьте, имеет ли активная ячейка previousSiblingElement nextSiblingElement или следующий nextSiblingElement. Для вверх и вниз, вы можете использовать текущую активную форму в качестве опорной точки и сравнить его с другой коробкой getBoundingClientRect() s, метод DOM, который возвращает geomoetry элемента относительно к браузерам просмотра.

При попытке подняться вверх, начните с якоря и пересчитайте предметы по направлению к 0. При движении вниз, начните с якоря и подсчитывайте до конца количества предметов. Это связано с тем, что при перемещении мы занимаемся только коробками перед активным ящиком, а при спуске мы заботимся о коробках после него. Все, что нам нужно искать, это коробка, которая имеет ту же левую позицию с более высокой или нижней верхней позицией.

Ниже приведен пример, который прослушивает событие keydown в window и будет перемещать активное состояние в соответствии с нажатой клавишей со стрелкой. Это определенно можно сделать более сухим, но я разделил четыре случая, чтобы вы могли видеть точную логику в каждом. Вы можете удерживать клавиши со стрелками вниз, чтобы поле двигалось непрерывно, и вы можете видеть его очень эффектным. И я обновил ваш JSBin своим решением здесь: http://jsbin.com/senigudoqu/1/edit?html,css,js,output

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

Ответ 5

Хотя вы можете рассчитать, какой элемент вы ищете, я предлагаю вам найти элемент ниже. Преимущество этого заключается в том, что он даже будет работать, если ваши элементы не имеют одинаковой ширины.

Поэтому давайте подумаем об атрибутах элемента ниже. По сути, это первый элемент с более высоким 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)

Ответ 6

В этом примере предполагается, что конечные точки движения ограничены. Кроме того, при переходе от второй к последней строке до последней строки, но в последней строке меньше столбцов, она будет перемещаться в последний столбец последней строки.

Это решение отслеживает строки/столбцы и использует объект сетки, чтобы отслеживать, где находятся элементы. Позиции будут обновляться в объекте сетки при изменении размера страницы.

(вы можете увидеть обновление упаковки в действии в полноэкранном режиме)

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>

Ответ 7

Я знаю, что это не совсем то, о чем просит ОП, но я хотел показать возможную альтернативу (зависит от варианта использования).

Вместо использования CSS flexbox существует также более новая CSS-сетка, которая фактически содержит столбцы и строки. Таким образом, преобразуя структуру в сетку и используя некоторый JS для прослушивания нажатых клавиш, активный элемент может быть перемещен (см. Неполный рабочий пример ниже).

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

Ответ 8

Вы можете использовать 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. Мы не можем просто вызвать функцию фильтра в списке узлов, который возвращается QSA (селектор запросов), поскольку браузеры возвращают списки узлов вместо массивов, а списки узлов не являются подходящими массивами.

Затем оператор if просто фильтрует все узлы и возвращает только те, которые имеют тот же самый offsetTop что и первый узел. т.е. всех узлов в первой строке.

Теперь у нас есть массив, из которого мы можем определить длину строки.

Я пропустил реализацию обхода DOM, поскольку это просто, используя либо чистый Javascript, либо JQuery и т.д., И не был частью вопроса OP. Я хотел бы только отметить, что важно проверить, должен ли элемент, который вы собираетесь переместить, существовать до его перемещения.

Эта функция будет работать с любым способом компоновки. Flexbox, float, CSS grid, независимо от будущего.

Рекомендации

Почему document.querySelectorAll возвращает StaticNodeList, а не реальный массив?

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

Ответ 9

offsetTop - популярный метод определения y-позиции элемента.

Если два смежных элемента sibling имеют одинаковое положение 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;
}

Ответ 10

В этом примере предполагается, что конечные точки движения ограничены. Кроме того, при переходе от второй к последней строке до последней строки, но в последней строке меньше столбцов, она будет перемещаться в последний столбец последней строки.

Это решение отслеживает строки/столбцы и использует объект сетки, чтобы отслеживать, где находятся элементы.

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>

Ответ 11

Если вы используете 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
}