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
This example assumes movement ends at the bounds. Also, if moving from the second to last row down to the last row, but there are fewer columns in the last row, it will move to the last column of the last row instead.
This solution keeps track of row/columns and uses a grid object to keep track of where the elements are. The positions will be updated in the grid object when the page is resized.
(you can see the wrapping update in action in full-screen mode)
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: #ffffd;
padding: 10px 0 0 10px;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}