58 votes

Comment calculer la quantité d'éléments flexbox dans une rangée?

Une grille est mise en œuvre à l'aide de la CSS flexbox. Exemple:

enter image description here

Le nombre de lignes dans cet exemple est de 4 parce que je fixe la largeur du conteneur à des fins de démonstration. Mais, dans la réalité, il peut changer en fonction du conteneur largeur (par exemple, si l'utilisateur redimensionne la fenêtre). Essayez de redimensionner la fenêtre de Sortie dans cet exemple pour se faire une idée.

Il y a toujours un élément actif, marqué avec la bordure noire.

À l'aide de JavaScript, j'ai autoriser les utilisateurs à naviguer vers l'élément précédent/suivant à l'aide de la flèche gauche/droite. Dans mon application, j'ai juste diminuer/augmenter l'indice de l'élément actif de 1.

Maintenant, j'aimerais permettre aux utilisateurs de naviguer vers le haut/vers le bas aussi bien. Pour cela, j'ai juste besoin pour diminuer/augmenter l'indice de l'élément actif en <amount of items in a row>. Mais, comment dois-je calculer ce nombre étant donné qu'elle dépend du conteneur de la largeur? Est-il une meilleure façon de mettre en œuvre le haut/bas de la fonctionnalité?

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

53voto

Brett DeWoody Points 3742

La question est un peu plus complexe que de trouver le nombre d'éléments dans une rangée.

En fin de compte, nous voulons savoir si il y a un élément ci-dessus, ci-dessous, à gauche et à droite de l'élément actif. Et cela doit prendre en compte les cas où la ligne du bas est incomplète. Par exemple, dans le cas ci-dessous, l'élément actif n'a pas de point ci-dessus, en dessous ou à droite:

enter image description here

Mais, afin de déterminer s'il y a un point au-dessus/ci-dessous/à gauche/à droite de l'élément actif, nous avons besoin de savoir combien d'éléments dans une rangée.

Trouver le nombre d'éléments par ligne

Pour obtenir le nombre d'éléments par ligne nous avons besoin de:

  • itemWidth - l' outerWidth d'un seul élément, y compris border, padding et margin
  • gridWidth - l' innerWidth de la grille, à l'exclusion border, padding et margin

Pour calculer ces deux valeurs avec du JavaScript que l'on peut utiliser:

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

Ensuite, nous pouvons calculer le nombre d'éléments par ligne à l'aide de:

const numPerRow = Math.floor(gridWidth / itemWidth)

Remarque: cela ne fonctionne que pour les uniformes de taille moyenne, et seulement si l' margin est définie en px des parts.

Beaucoup, Beaucoup, Beaucoup Plus Simple

Sur l'ensemble de ces largeurs, et les rembourrages, les marges et les frontières, c'est vraiment déroutant. Il y a beaucoup, beaucoup, beaucoup plus simple solution.

Nous avons seulement besoin de trouver l'index de l'élément de grille qui est - offsetTop de la propriété est plus grande que la première grille de l'élément 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);

Le ternaire à la fin des comptes pour les cas où il n'y a qu'un seul élément de la grille, et/ou une seule rangée d'éléments.

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>

Mais est-il un élément au-dessus ou en-dessous?

Pour savoir si il y a un point au-dessus ou au-dessous de l'élément actif, nous avons besoin de savoir 3 paramètres:

  • totalItemsInGrid
  • activeIndex
  • numPerRow

Par exemple, dans la structure suivante:

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

nous avons un totalItemsInGrid de 5, l' activeIndex a un index de base zéro de l' 2 (c'est le 3ème élément dans le groupe), et disons que l' numPerRow est de 3.

Nous pouvons maintenant déterminer si un élément est au dessus, en dessous, à gauche ou à droite de l'élément actif avec:

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

Si isTopRow est true on ne peut pas se déplacer jusqu', et si isBottomRow est true on ne peut pas se déplacer vers le bas. Si isLeftColumn est true on ne peut pas se déplacer à gauche, et si isRightColumn si true nous ne pouvons pas nous déplacer vers la droite.

Remarque: isBottomRow n'est pas seulement de vérifier si l'élément actif est sur la ligne du bas, mais vérifie également si il y a un élément en dessous. Dans notre exemple ci-dessus, l'élément actif est pas sur la ligne du bas, mais n'a pas un point en-dessous d'elle.

Un Exemple De Travail

J'ai travaillé dans un exemple complet qui fonctionne avec le redimensionnement et l' #grid élément redimensionnable, de sorte qu'il peut être testé dans l'extrait de code ci-dessous.

J'ai créé une fonction, navigateGrid qui accepte trois paramètres:

  • gridSelector - un DOM sélecteur pour l'élément de grille
  • activeClass - le nom de la classe de l'élément actif
  • direction - l'un d' up, down, leftou right

Il peut être utilisé comme 'navigateGrid("#grid", "active", "up") avec la structure HTML de votre question.

La fonction calcule le nombre de lignes à l'aide de l' offset méthode, puis ne la vérifie si l' active élément peut être modifié pour le haut/bas/gauche/droite de l'élément.

En d'autres termes, la fonction vérifie si l'élément actif peut être déplacé vers le haut/bas et gauche/droite. Cela signifie:

  • ne peut pas aller à gauche de la colonne la plus à gauche
  • peut pas aller à droite de la colonne la plus à droite
  • ne peut pas monter à partir de la rangée du haut
  • ne peut pas aller vers le bas à partir de la rangée du bas, ou si la cellule ci-dessous est vide

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>

24voto

Temani Afif Points 69370

(Pour une expérience optimale mieux de courir à la interactives des extraits sur la page complète)

Calcul du nombre d'éléments par ligne

Vous avez besoin pour obtenir la largeur d'un élément avec sa marge (éventuellement frontière s'ils sont mis aussi) alors vous avez besoin pour obtenir la largeur intérieure du récipient sans rembourrage. Le fait d'avoir ces 2 valeurs vous faire une simple division pour obtenir le nombre d'élément par ligne.

N'oubliez pas de considérer le cas où vous avez une seule ligne, alors vous avez besoin pour obtenir la valeur minimale entre le nombre total d'éléments et le nombre que vous obtenez de la division.

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

Voici une version jQuery de la même logique avec moins de code:

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

Et voici une démonstration de l'interactif de la grille:

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>

Une Autre Idée

Nous pouvons également envisager une autre façon de naviguer à l'intérieur de la grille sans la nécessité de le nombre d'élément par ligne. L'idée est de s'appuyer sur la fonction elementFromPoint(x,y).

La logique est la suivante: Nous sommes à l'intérieur d'un élément actif et nous avons ses (x,y) position. En appuyant sur une touche, nous allons augmenter/diminuer ces valeurs et nous utilisons la fonction ci-dessus pour obtenir le nouvel élément à l'aide de la nouvelle - (x,y). Nous testons si nous obtenons un élément valide et si cet élément est un élément (contient item de la classe). Dans ce cas, nous supprimer active de la précédente et nous l'ajoutons à la nouvelle.

Voici un exemple où je n'envisager un à l'intérieur de navigation. Lorsque nous arrivons à gauche/droite limites du conteneur, nous n'obtiendrez pas de précédent/suivant de la ligne:

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>

Comme vous pouvez le remarquer dans cette méthode, nous n'avons pas besoin de l'information sur le conteneur, la taille de l'écran, le numéro de l'élément, etc. La seule information nécessaire est la dimension d'un seul élément. Nous avons également besoin d'un petit code pour corriger la position de l'élément actif de la fenêtre de redimensionnement.


Bonus

Voici un autre de fantaisie idée si vous voulez avoir un visuellement élément actif sans la nécessité de l'ajout d'une classe, ou avec JS. L'idée est d'utiliser le fond du récipient pour créer une boîte noire derrière l'élément actif.

Par ailleurs, cette méthode a 2 inconvénients:

  1. Pas facile de faire face à la dernière ligne, si c'est pas complètement de l'élément que nous pouvons avoir de la boîte noire derrière rien
  2. Nous devons tenir compte de l'espace à gauche après le dernier élément de chaque ligne pour éviter d'avoir une position étrange de la boîte noire.

Voici un code simplifié avec une hauteur fixe/largeur de l'emballage:

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>

Comme nous pouvons le voir, le code est relativement simple de sorte qu'il peut être adapté à la situation où presque toutes les valeurs sont connues et fixes.

9voto

Munim Munna Points 13181

La seule façon de se déplacer en haut et en bas qui se pose moins indésirable complication, à ma connaissance, est d'avoir le nombre de cases par ligne et de modification de l'index. Le seul problème est que vous avez besoin de calculer le boxcount sur les deux fenêtre de la charge et de l'événement de redimensionnement.

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

Maintenant, si vous voulez un moyen très simple d'obtenir le nombre de cases dans une rangée sans même se soucier de la taille de ni le conteneur ni les boîtes, oubliez les marges externes et internes, vous pouvez vérifier combien de cases sont alignées avec la première zone de comparaison de la propriété offsetTop.

Le HTMLElement.offsetTop propriété en lecture seule retourne la distance de l'élément courant par rapport au haut de la offsetParent nœud. [source: développeur.mozilla.orgl]

Vous pouvez la mettre en œuvre comme ci-dessous:

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

Plein de travail exemple:

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

4voto

skyline3000 Points 5299

À l'appui de déplacement haut, bas, gauche et droite, vous n'avez pas besoin de savoir de combien de cases il y a dans une rangée, vous avez juste besoin de calculer si il y a un encadré ci-dessus, ci-dessous, à gauche ou à droite de la zone active.

Se déplaçant de gauche et de droite est simple comme vous l'avez remarqué - il suffit de vérifier si la zone active a un previousSiblingElement ou nextSiblingElement. Pour le haut et vers le bas, vous pouvez utiliser l'active de la zone comme un point d'ancrage et de le comparer à l'autre de la zone de getBoundingClientRect()s, un DOM méthode qui retourne le geomoetry d'un élément par rapport à la fenêtre d'affichage du navigateur.

Lorsque vous essayez de déplacer vers le haut, commencer à l'ancrage et le compte à rebours à travers les éléments vers 0. En cas de déplacement vers le bas, commencer à l'ancrage et à compter jusqu'à la fin du nombre d'éléments. C'est parce que lorsqu'ils se déplacent, nous ne se soucient que des boîtes avant de la zone active, et lors de la descente de nous ne se soucient que des boîtes d'après elle. Tous nous avons besoin à considérer est une boîte qui a la même position la plus à gauche avec une augmentation ou une diminution de la position supérieure.

Ci-dessous est un exemple qui est à l'écoute d'un événement keydown sur window et passe de l'état actif selon laquelle la flèche touche a été pressée. Il pourrait certainement être plus SEC, mais j'ai divisé les quatre cas, de sorte que vous pouvez voir exactement la logique de chacun. Vous pouvez maintenir les touches fléchées vers le bas de sorte que la zone se déplace en permanence, et vous pouvez le voir, il est très performant. Et j'ai mis à jour votre JSBin avec ma solution ici:http://jsbin.com/senigudoqu/1/edit?html,css,js,sortie

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>

2voto

Krutius Points 750

Alors que vous pouvez calculer quel élément vous recherchez, je vous suggère de rechercher l'élément ci-dessous. L'avantage de ceci est qu'il fonctionnerait même si vos éléments n'ont pas la même largeur.

Pensons donc aux attributs de l'élément ci-dessous sont. Essentiellement, c'est le premier élément avec un plus élevé et le même . Vous pouvez faire quelque chose comme ça pour trouver l'élément sur le dessus:

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X