que froggy bootstrap javascript html css css3 flexbox

javascript - froggy - flexbox que es



¿Cómo calcular la cantidad de elementos de flexbox en una fila? (11)

Se implementa una grilla utilizando CSS flexbox. Example:

El número de filas en este ejemplo es 4 porque fijé el ancho del contenedor para propósitos de demostración. Pero, en realidad, puede cambiar en función del ancho del contenedor (por ejemplo, si el usuario cambia el tamaño de la ventana). Intente cambiar el tamaño de la ventana de salida en este ejemplo para tener una idea.

Siempre hay un elemento activo, marcado con el borde negro.

Usando JavaScript, les permito a los usuarios navegar al elemento anterior / siguiente usando la flecha izquierda / derecha. En mi implementación, simplemente disminuyo / aumento el índice del elemento activo en 1.

Ahora, me gustaría permitir a los usuarios navegar hacia arriba / abajo también. Para eso, solo necesito disminuir / aumentar el índice del elemento activo en <amount of items in a row> . Pero, ¿cómo puedo calcular este número dado que depende del ancho del contenedor? ¿Hay una mejor manera de implementar la funcionalidad de subir / bajar?

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


Este ejemplo supone que el movimiento termina en los límites. Además, si se mueve desde la segunda a la última fila hasta la última fila, pero hay menos columnas en la última fila, en su lugar se moverá a la última columna de la última fila.

Esta solución realiza un seguimiento de las filas / columnas y utiliza un objeto de cuadrícula para realizar un seguimiento de dónde están los elementos. Las posiciones se actualizarán en el objeto de la cuadrícula cuando se cambie el tamaño de la página.

(Puede ver la actualización de envoltura en acción en modo de pantalla completa)

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>


La única forma de moverse hacia arriba y hacia abajo que surge menos complicaciones no deseadas, que yo sepa, es contar los cuadros por fila y cambiar los índices. El único problema es que necesita calcular el conteo de cajas tanto en la carga de la ventana como en el evento de cambio de tamaño.

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

Ahora bien, si desea una manera muy simple de obtener la cantidad de cajas seguidas sin preocuparse por el tamaño ni del contenedor ni de las cajas, olvide los márgenes y los rellenos , puede verificar cuántas cajas están alineadas con la primera caja comparando el offsetTop propiedad .

La propiedad de solo lectura HTMLElement.offsetTop devuelve la distancia del elemento actual en relación con la parte superior del nodo offsetParent. [fuente: developer.mozilla.orgl ]

Puedes implementarlo como a continuación:

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; ‎ } }

Ejemplo completo de trabajo:

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


La pregunta es un poco más compleja que encontrar cuántos artículos hay en una fila.

En última instancia, queremos saber si hay un elemento arriba, abajo, a la izquierda y a la derecha del elemento activo. Y esto necesita tener en cuenta los casos en que la fila inferior está incompleta. Por ejemplo, en el caso siguiente, el elemento activo no tiene ningún elemento arriba, abajo o a la derecha:

Pero, para determinar si hay un elemento arriba / abajo / izquierda / derecha del artículo activo, necesitamos saber cuántos artículos están en una fila.

Encuentra la cantidad de artículos por fila

Para obtener la cantidad de elementos por fila, necesitamos:

  • itemWidth : el outerWidth de un solo elemento, incluidos el border , el padding y el margin
  • gridWidth - el innerWidth de la grilla, excluyendo border , padding y margin

Para calcular estos dos valores con JavaScript, podemos usar:

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

Entonces podemos calcular la cantidad de elementos por fila usando:

const numPerRow = Math.floor(gridWidth / itemWidth)

Nota: esto solo funcionará para artículos de tamaño uniforme, y solo si el margin está definido en px unidades.

Un enfoque mucho, mucho, mucho más simple

Tratar con todos estos anchos, y paddings, márgenes y bordes es realmente confuso. Hay una solución mucho, mucho, mucho más simple.

Solo necesitamos encontrar el índice del elemento de cuadrícula offsetTop propiedad offsetTop sea ​​mayor que offsetTop del primer elemento de 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);

El ternario al final representa los casos en los que solo hay un elemento en la cuadrícula y / o una sola fila de artículos.

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>

¿Pero hay un artículo arriba o abajo?

Para saber si hay un elemento encima o debajo del elemento activo, necesitamos saber 3 parámetros:

  • totalItemsInGrid
  • activeIndex
  • numPerRow

Por ejemplo, en la siguiente estructura:

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

tenemos un totalItemsInGrid de 5 , el activeIndex tiene un índice basado en cero de 2 (es el tercer elemento del grupo), y digamos que numPerRow es 3.

Ahora podemos determinar si hay un elemento arriba, abajo, a la izquierda o a la derecha del elemento activo con:

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

Si isTopRow es true no podemos movernos hacia arriba, y si isBottomRow es true no podemos movernos hacia abajo. Si isLeftColumn es true no podemos mover hacia la izquierda, y si isRightColumn es true no podemos movernos a la derecha.

Nota : isBottomRow no solo verifica si el elemento activo está en la fila inferior, sino que también verifica si hay isBottomRow elemento debajo. En nuestro ejemplo anterior, el elemento activo no está en la fila inferior, pero no tiene un elemento debajo.

Un ejemplo de trabajo

He trabajado esto en un ejemplo completo que funciona con cambio de tamaño, e hizo que el elemento #grid se pueda redimensionar para que pueda probarse en el siguiente fragmento.

Creé una función, navigateGrid que acepta tres parámetros:

  • gridSelector - un selector de DOM para el elemento de la grilla
  • activeClass - el nombre de clase del elemento activo
  • direction - una de up , down , left o right

Esto se puede utilizar como ''navigateGrid("#grid", "active", "up") con la estructura HTML de su pregunta.

La función calcula el número de filas usando el método de offset , luego realiza las comprobaciones para ver si el elemento active puede cambiarse al elemento arriba / abajo / izquierda / derecha.

En otras palabras, la función comprueba si el elemento activo se puede mover arriba / abajo e izquierda / derecha. Esto significa:

  • no puede ir a la izquierda desde la columna de la izquierda
  • no puede ir directamente desde la columna de la derecha
  • no puede subir desde la fila superior
  • no puede bajar desde la fila inferior, o si la celda de abajo está vacía

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>


Mientras puede calcular qué elemento está buscando, le sugiero que busque el elemento a continuación. El beneficio de esto es que incluso funcionaría si sus elementos no tienen el mismo ancho.

Entonces, pensemos en los atributos del elemento a continuación. Esencialmente es el primer elemento con un offsetTop más offsetTop y el mismo offsetLeft . Puedes hacer algo como esto para encontrar el elemento 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)


Para admitir desplazamientos hacia arriba, hacia abajo, hacia la izquierda y hacia la derecha, no necesita saber cuántas casillas hay en una fila, solo necesita calcular si hay un recuadro arriba, abajo, a la izquierda o a la derecha del recuadro activo .

Mover hacia la izquierda y hacia la derecha es simple, como nextSiblingElement notado: solo verifica si el recuadro activo tiene un previousSiblingElement o nextSiblingElement . Para subir y bajar, puede usar el cuadro activo actual como punto de anclaje y compararlo con getBoundingClientRect() s del otro cuadro, un getBoundingClientRect() que devuelve la geomoesencia de un elemento relativo a la ventana del navegador.

Cuando intente moverse hacia arriba, comience en el anclaje y cuente hacia abajo a través de los elementos hacia 0. Al bajar, comience en el ancla y cuente hasta el final de la cantidad de elementos. Esto se debe a que cuando nos movemos hacia arriba, solo nos importan las casillas antes de la casilla activa, y cuando baja solo nos importan las cajas después de ella. Todo lo que debemos buscar es una caja que tenga la misma posición a la izquierda con una posición superior más alta o más baja.

A continuación se muestra un ejemplo que escucha un evento de selección en la window y moverá el estado activo de acuerdo con la tecla de flecha que se presionó. Definitivamente podría hacerse más seco, pero he dividido los cuatro casos para que pueda ver la lógica exacta en cada uno. Puedes mantener presionadas las teclas de flecha para que la caja se mueva continuamente y puedas ver que es muy eficiente. Y he actualizado su JSBin con mi solución aquí: 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>


Sé que esto no es exactamente lo que OP está pidiendo, pero quería mostrar una posible alternativa (depende del caso de uso).

En lugar de usar CSS flexbox, también existe la cuadrícula CSS más reciente que en realidad presenta columnas y filas. Por lo tanto, al convertir la estructura en una cuadrícula y usar algunos JS para escuchar los botones de las teclas que se están presionando, el elemento activo se puede mover (vea el ejemplo de trabajo incompleto a continuación).

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>

Sin embargo, como se indicó anteriormente, esta solución tiene defectos. Por una vez, el elemento activo es en realidad un elemento de la grilla por sí mismo y se mueve a lo largo de la grilla con los otros elementos que fluyen a su alrededor. En segundo lugar, de forma similar al modelo de caja flexible, actualmente no hay selectores de CSS para orientar un artículo en función de su posición de cuadrícula.

Sin embargo, dado que estamos usando javascript de todos modos, puede recorrer todos los elementos de la grilla y obtener las propiedades de la cuadrícula CSS. Si coinciden con las coordenadas actuales, tiene su elemento objetivo. Lamentablemente, esto solo funcionaría si se coloca cada elemento, utilizando grid-column-start: auto for elements no ayuda. Incluso window.getComputedStyle() solo devolverá auto ;


offsetTop es un método popular para determinar la posición y de un elemento.

Si dos elementos hermanos adyacentes tienen la misma posición y, podemos suponer con seguridad que están visualmente en la misma fila (ya que todos los elementos tienen la misma altura).

Por lo tanto, podemos comenzar a contar el número de elementos en una fila comparando sus posiciones y una por una. Dejamos de contar tan pronto como nos quedemos sin elementos o nos encontramos con un hermano adyacente con una posición y diferente.

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


(Para una experiencia óptima, mejor ejecute los fragmentos interactivos en la página completa)

Cálculo del número de elementos por fila

Necesita obtener el ancho de un elemento con su margen (eventualmente borde si están configurados también), entonces necesita obtener el ancho interno del contenedor sin relleno . Tener estos 2 valores hace una división simple para obtener el número de elementos por fila.

No olvide considerar el caso en el que solo tiene una fila, por lo que debe obtener el valor mínimo entre la cantidad total de elementos y el número que obtiene de la división.

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

Aquí hay una versión jQuery de la misma lógica con menos código:

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

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

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

Y aquí hay una demostración de la grilla interactiva:

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

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

Otra idea

También podemos considerar otra forma de navegar dentro de la red sin la necesidad del número de elementos por fila. La idea es confiar en la función elementFromPoint(x,y) .

La lógica es la siguiente: estamos dentro de un elemento activo y tenemos su posición (x,y) . Al presionar una tecla aumentaremos / disminuiremos estos valores y usaremos la función anterior para obtener el nuevo elemento usando el nuevo (x,y) . Probamos si obtenemos un elemento válido y si este elemento es un elemento (contiene clase de item ) . En este caso, eliminamos el activo del anterior y lo agregamos al nuevo.

Aquí hay un ejemplo en el que solo considero una navegación interna . Cuando lleguemos al límite izquierdo / derecho del contenedor no llegaremos a la línea anterior / siguiente:

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

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

Como puede observar en este método, no necesitamos ninguna información sobre el contenedor, el tamaño de la pantalla, el número de elementos, etc. La única información necesaria es la dimensión de un solo elemento. También necesitamos un código pequeño para rectificar la posición del elemento activo en el cambio de tamaño de la ventana.

Prima

Aquí hay otra idea elegante si desea tener un elemento visualmente activo sin la necesidad de agregar una clase o obtenerla con JS. La idea es usar fondo en el contenedor para crear una caja negra detrás del elemento activo.

Por cierto, este método tiene 2 inconvenientes:

  1. No es fácil lidiar con la última línea si no está llena de elementos ya que podemos tener la caja negra detrás de nada
  2. Tenemos que considerar el espacio que queda después del último elemento de cada fila para evitar tener una posición extraña de la caja negra.

Aquí hay un código simplificado con un contenedor de altura / ancho fijo:

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

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

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

Como podemos ver, el código es bastante simple, por lo que puede ser adecuado para una situación en la que casi todos los valores son conocidos y corregidos.


Si está utilizando Jquery y confía en que sus objetos de cuadrícula estén alineados verticalmente, podría ser el truco.

No lo probé, pero debería funcionar (contando las columnas)

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 }


Este ejemplo supone que el movimiento termina en los límites. Además, si se mueve desde la segunda a la última fila hasta la última fila, pero hay menos columnas en la última fila, en su lugar se moverá a la última columna de la última fila.

Esta solución realiza un seguimiento de las filas / columnas y utiliza un objeto de cuadrícula para realizar un seguimiento de dónde están los elementos.

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>


Puede usar Array.prototype.filter () para hacer esto de manera bastante clara. Para obtener la cantidad de elementos en una fila, use esta función. Pase el selector de CSS que desea usar (en este caso, elemento). Una vez que tenga el tamaño de fila, la navegación de flecha es fácil.

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

Ejemplos

Demo de CodePen: https://codepen.io/gtlitc/pen/EExXQE

Demostración interactiva que muestra el tamaño de fila y mover cantidades. http://www.smallblue.net/demo/49043684/

Explicación

En primer lugar, la función establece una variable firstToppara que sea la offsetTopdel primer nodo.

A continuación, la función crea una matriz rowArrayde nodos en la primera fila (si la navegación hacia arriba y hacia abajo es posible, la primera fila siempre será una fila de longitud completa).

Esto se hace llamando (tomando prestado) la función de filtro del Array Prototype. No podemos simplemente llamar a la función de filtro en la lista de nodos que devuelve el QSA (selector de consulta) porque los navegadores devuelven listas de nodos en lugar de matrices y las listas de nodos no son matrices adecuadas.

La sentencia if simplemente filtra todos los nodos y solo devuelve los que tienen el mismo offsetTopque el primer nodo. es decir, todos los nodos en la primera fila.

Ahora tenemos una matriz desde la cual podemos determinar la longitud de una fila.

He omitido la implementación del recorrido DOM ya que esto es simple usando Javascript puro o Jquery, etc. y no era parte de la pregunta de OPs. Solo señalaría que es importante probar si el elemento al que pretende mudarse existe antes de trasladarse allí.

Esta función funcionará con cualquier técnica de diseño. Flexbox, float, grid de CSS, lo que sea que depara el futuro.

Referencias

¿Por qué document.querySelectorAll devuelve StaticNodeList en lugar de una matriz real?

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