javascript - tag - webkit flex shrink




Come calcolare la quantità di elementi della flexbox in una riga? (8)

L'unico modo per muoversi su e giù che presenta una complicazione meno indesiderata a mia conoscenza è il conteggio delle caselle per riga e la modifica degli indici. L'unico problema è che devi calcolare il conteggio delle scatole su entrambi gli eventi di caricamento e ridimensionamento della finestra.

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

Ora se vuoi un modo molto semplice per ottenere il numero di scatole di fila senza preoccuparti delle dimensioni né del contenitore né delle scatole, dimentica i margini e i paddings , puoi verificare quante scatole sono allineate con la prima casella che confronta il offsetTop proprietà .

La proprietà di sola lettura HTMLElement.offsetTop restituisce la distanza dell'elemento corrente rispetto all'inizio del nodo offsetParent. [fonte: developer.mozilla.orgl ]

Puoi implementarlo come di seguito:

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

Esempio operativo completo:

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

Una griglia viene implementata utilizzando la flexbox CSS. Example:

Il numero di righe in questo esempio è 4 perché ho corretto la larghezza del contenitore per scopi dimostrativi. Ma, in realtà, può cambiare in base alla larghezza del contenitore (ad es. Se l'utente ridimensiona la finestra). Prova a ridimensionare la finestra Output in questo esempio per avere un'idea.

C'è sempre un oggetto attivo, contrassegnato con il bordo nero.

Usando JavaScript, consento agli utenti di navigare alla voce precedente / successiva usando la freccia sinistra / destra. Nella mia implementazione, faccio solo diminuire / aumentare l'indice dell'elemento attivo di 1.

Ora, vorrei consentire agli utenti di navigare su / giù pure. Per questo, ho solo bisogno di diminuire / aumentare l'indice dell'elemento attivo di <amount of items in a row> . Ma come faccio a calcolare questo numero dato che dipende dalla larghezza del contenitore? C'è un modo migliore per implementare la funzionalità su / giù?

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


La domanda è leggermente più complessa della ricerca di quanti oggetti ci sono in fila.

In definitiva, vogliamo sapere se c'è un elemento sopra, sotto, a sinistra ea destra dell'elemento attivo. E questo deve tener conto dei casi in cui la riga inferiore è incompleta. Ad esempio, nel caso seguente, l'elemento attivo non ha elementi sopra, sotto o destra:

Ma, per determinare se c'è un oggetto sopra / sotto / sinistra / destra dell'elemento attivo, dobbiamo sapere quanti oggetti ci sono in fila.

Trova il numero di articoli per riga

Per ottenere il numero di articoli per riga abbiamo bisogno di:

  • itemWidth : la outerWidth di un singolo elemento tra cui border , padding e margin
  • gridWidth - the innerWidth della griglia, esclusi border , padding e margin

Per calcolare questi due valori con un semplice JavaScript possiamo usare:

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

Quindi possiamo calcolare il numero di elementi per riga usando:

const numPerRow = Math.floor(gridWidth / itemWidth)

Nota: questo funziona solo per articoli di dimensioni uniformi e solo se il margin è definito in unità px .

Un approccio molto, molto, molto più semplice

Occuparsi di tutte queste larghezze, paddings, margini e bordi è davvero confuso. C'è una soluzione molto, molto, molto più semplice.

Abbiamo solo bisogno di trovare l'indice dell'elemento della griglia che ha la proprietà offsetTop maggiore del primo offsetTop dell'elemento della 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);

Il ternario alla fine tiene conto dei casi in cui è presente un solo elemento nella griglia e / o una singola riga di elementi.

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>

Ma c'è un oggetto sopra o sotto?

Per sapere se c'è un elemento sopra o sotto l'elemento attivo abbiamo bisogno di conoscere 3 parametri:

  • totalItemsInGrid
  • activeIndex
  • numPerRow

Ad esempio, nella seguente struttura:

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

abbiamo un totalItemsInGrid di 5 , l' activeIndex ha un indice a base zero di 2 (è il terzo elemento nel gruppo), e diciamo che numPerRow è 3.

Ora possiamo determinare se c'è un elemento sopra, sotto, a sinistra o a destra dell'elemento attivo con:

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

Se isTopRow è true non possiamo andare su e se isBottomRow è true non possiamo andare giù. Se isLeftColumn è true non possiamo spostarci a sinistra, e se isRightColumn se true non possiamo spostarci a destra.

Nota : isBottomRow non controlla solo se l'elemento attivo si trova nella riga inferiore, ma controlla anche se vi sia un elemento al di sotto di esso. Nel nostro esempio sopra, l'elemento attivo non si trova nella riga inferiore, ma non ha un elemento al di sotto di esso.

Un esempio di lavoro

Ho lavorato a questo in un esempio completo che funziona con il ridimensionamento e reso ridimensionabile l'elemento #grid modo che possa essere testato nello snippet seguente.

Ho creato una funzione, navigateGrid che accetta tre parametri:

  • gridSelector - un selettore DOM per l'elemento della griglia
  • activeClass : il nome della classe dell'elemento attivo
  • direction : uno up , down , left o right

Questo può essere usato come 'navigateGrid("#grid", "active", "up") con la struttura HTML dalla tua domanda.

La funzione calcola il numero di righe usando il metodo offset , quindi controlla per vedere se l'elemento active può essere modificato sull'elemento su / giù / sinistra / destra.

In altre parole, la funzione controlla se l'elemento attivo può essere spostato su / giù e sinistra / destra. Questo significa:

  • non può andare a sinistra dalla colonna più a sinistra
  • non può andare diritto dalla colonna più a destra
  • non può salire dalla riga superiore
  • non può scendere dalla riga inferiore, o se la cella sottostante è vuota

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>


Per supportare lo spostamento verso l'alto, il basso, sinistra e destra, non è necessario sapere quante caselle ci sono in una riga, devi solo calcolare se c'è una casella sopra, sotto, sinistra o destra della casella attiva .

Spostarsi a sinistra e a destra è semplice, come hai notato, basta controllare se la casella attiva ha un oggetto previousSiblingElement nextSiblingElement o successivo. Per su e giù, è possibile utilizzare la casella attiva corrente come punto di ancoraggio e confrontarla con l'altro getBoundingClientRect() s, un getBoundingClientRect() che restituisce la geomeetria di un elemento rispetto alla finestra del browser.

Quando provi a salire, inizia dall'ancora e contrai verso il basso attraverso gli oggetti verso 0. Quando ti sposti verso il basso, inizia dall'ancora e conta fino alla fine del numero di elementi. Questo perché quando ci spostiamo verso l'alto, ci occupiamo solo delle caselle prima del box attivo, e quando scendiamo ci occupiamo solo delle caselle dopo di esso. Tutto ciò che dobbiamo cercare è una scatola che abbia la stessa posizione a sinistra con una posizione superiore o inferiore in alto.

Di seguito è riportato un esempio che ascolta un evento keydown sulla window e sposta lo stato attivo in base a quale tasto freccia è stato premuto. Potrebbe sicuramente essere reso più ASCIUTTO, ma ho diviso i quattro casi in modo da poter vedere la logica esatta in ciascuno. Puoi tenere premuti i tasti freccia in modo che la scatola si muova continuamente e puoi vedere che è molto performante. E ho aggiornato il tuo JSBin con la mia soluzione qui: 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>


Questo esempio presuppone che il movimento finisca ai limiti. Inoltre, se ci si sposta dalla penultima all'ultima riga, ma ci sono meno colonne nell'ultima riga, si sposterà invece sull'ultima colonna dell'ultima riga.

Questa soluzione tiene traccia di righe / colonne e utilizza un oggetto griglia per tenere traccia di dove si trovano gli elementi. Le posizioni verranno aggiornate nell'oggetto griglia quando la pagina viene ridimensionata.

(puoi vedere l'aggiornamento del wrapping in azione in modalità a schermo intero)

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>


offsetTop è un metodo popolare per determinare la posizione y di un elemento.

Se due elementi fratelli adiacenti hanno la stessa posizione y, possiamo tranquillamente supporre che siano visivamente sulla stessa riga (poiché tutti gli elementi hanno la stessa altezza).

Quindi, possiamo iniziare a contare il numero di elementi in una fila confrontando le loro posizioni y una per una. Smettiamo di contare non appena finiamo gli elementi o incontriamo un fratello adiacente con una diversa posizione 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;
}

(Per un'esperienza ottimale, è meglio eseguire gli snippet interattivi su tutta la pagina)

Calcolo del numero di elementi per riga

È necessario ottenere la larghezza di un elemento con il suo margine (eventualmente border se sono impostati anche), quindi è necessario ottenere la larghezza interna del contenitore senza padding . Avendo questi 2 valori fai una semplice divisione per ottenere il numero di elementi per riga.

Non dimenticare di considerare il caso in cui hai solo una riga, quindi devi ottenere il valore minimo tra il numero totale di elementi e il numero che ottieni dalla divisione.

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

Ecco una versione jQuery della stessa logica con meno codice:

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

Ed ecco una dimostrazione della griglia interattiva:

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>

Un'altra idea

Possiamo anche considerare un altro modo per navigare all'interno della griglia senza la necessità del numero di elementi per riga. L'idea è di fare affidamento sulla funzione elementFromPoint(x,y) .

La logica è la seguente: siamo all'interno di un elemento attivo e abbiamo la sua posizione (x,y) . Premendo un tasto aumenteremo / diminuiremo questi valori e useremo la funzione sopra per ottenere il nuovo elemento usando il nuovo (x,y) . Testiamo se otteniamo un elemento valido e se questo elemento è un elemento (contiene la classe item ) . In questo caso rimuoviamo attivo da quello precedente e lo aggiungiamo a quello nuovo.

Ecco un esempio in cui considero solo una navigazione interna . Quando raggiungiamo il limite sinistro / destro del container non arriveremo alla riga precedente / successiva:

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>

Come puoi notare in questo metodo, non abbiamo bisogno di informazioni sul contenitore, le dimensioni dello schermo, il numero di elementi, ecc. Le sole informazioni necessarie sono le dimensioni di un singolo oggetto. Abbiamo anche bisogno di un piccolo codice per rettificare la posizione dell'elemento attivo sul ridimensionamento della finestra.

indennità

Ecco un'altra idea di fantasia se vuoi avere un elemento visivamente attivo senza la necessità di aggiungere una classe o di ottenerla con JS. L'idea è di usare lo sfondo sul contenitore per creare una scatola nera dietro l'elemento attivo.

A proposito, questo metodo ha 2 svantaggi:

  1. Non è facile trattare con l'ultima riga se non è piena di elementi, perché potremmo avere la scatola nera dietro a nulla
  2. Dobbiamo considerare lo spazio rimasto dopo l'ultimo elemento di ogni riga per evitare di avere una strana posizione della scatola nera.

Ecco un codice semplificato con un contenitore di altezza / larghezza fisso:

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>

Come possiamo vedere il codice è piuttosto semplice, quindi può essere adatto a situazioni del genere in cui quasi tutti i valori sono noti e risolti.


Potresti usare Array.prototype.filter () per farlo in modo abbastanza ordinato. Utilizzare la funzione per ottenere la quantità di elementi in una riga. Passare nel selettore CSS che si desidera utilizzare (in questo caso .item). Una volta ottenuta la dimensione della riga, la navigazione con la freccia è facile.

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

Esempi

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

Demo interattivo che mostra le dimensioni della riga e gli importi delle mosse. http://www.smallblue.net/demo/49043684/

Spiegazione

Innanzitutto la funzione imposta una variabile firstTopcome il offsetTopprimo nodo.

Successivamente la funzione crea una serie rowArraydi nodi nella prima riga (se è possibile la navigazione su e giù, la prima riga sarà sempre una riga intera).

Questo viene fatto chiamando (prendendo in prestito) la funzione filtro dal Prototipo di Array. Non possiamo semplicemente chiamare la funzione filtro sulla lista dei nodi che viene restituita da QSA (query selector all) perché i browser restituiscono gli elenchi dei nodi invece degli array e gli elenchi dei nodi non sono matrici appropriate.

L'istruzione if quindi filtra semplicemente tutti i nodi e restituisce solo quelli che hanno lo stesso offsetTopcome il primo nodo. cioè tutti i nodi nella prima riga.

Ora abbiamo una matrice da cui possiamo determinare la lunghezza di una riga.

Ho omesso l'implementazione del DOM traversal in quanto è semplice utilizzare Javascript puro o Jquery etc e non faceva parte della domanda OPs. Vorrei solo notare che è importante verificare se l'elemento che si intende spostare esiste prima di trasferirsi lì.

Questa funzione funziona con qualsiasi tecnica di layout. Flexbox, float, griglia CSS, qualunque sia il futuro.

Riferimenti

Perché document.querySelectorAll restituisce uno StaticNodeList piuttosto che una matrice reale?

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


Questo esempio presuppone che il movimento finisca ai limiti. Inoltre, se ci si sposta dalla penultima all'ultima riga, ma ci sono meno colonne nell'ultima riga, si sposterà invece sull'ultima colonna dell'ultima riga.

Questa soluzione tiene traccia di righe / colonne e utilizza un oggetto griglia per tenere traccia di dove si trovano gli elementi.

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>





flexbox