How to move on another div segment in HTML with arrow keys?

Viewed 51

I have three main div boxes pink, red and green. 

I want to make all segments moving with arrow keys. For example: when we load the page, active box would be in segment 'pink' in the middle. And with arrow keys we can go up/down/right/left. This is implemented in code below.

But I need one more task to do, when we go to right last box in segment 'pink' on next right press we want to move to 'red' segment. There is one box so when I will press left button we will go to the 'pink' segment back. We are in segment 'pink' and press array down to last box in 'pink' segment so then after pressing down button we need to go to 'green' segment and then be able to moving around three boxes in there.

And also, if we are at 'green' segment and go to last right box and press right key then I need nothing. Also with left box in 'green' segment. Go outside from 'green' segment only with UP arrow key

Is there any appropriate ways to do that?

const boxContainer = document.getElementById('tails')

const selected = {
  row: 2,
  column: 2,
}

const Clamp = (num, min, max) => Math.min(Math.max(num, min), max)

const ActivateSelection = (prev) => {
  const box = boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`)

  box.classList.add('hover')

  if (!prev) return
  if (prev.row === selected.row && prev.column === selected.column) return

  const prevBox = boxContainer.querySelector(`.boxGroup:nth-child(${prev.row}) .box:nth-child(${prev.column})`)

  prevBox.classList.remove('hover')
}

const OnKeyDown = event => {
  const maxRow = boxContainer.querySelectorAll('.boxGroup').length
  const maxCol = boxContainer.querySelectorAll(`.boxGroup:nth-child(${selected.row}) .box`).length

  const prevBox = { ...selected
  }

  switch (event.key) {
    case "ArrowUp":
      boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`).classList.remove('active')

      selected.row = Clamp(selected.row - 1, 1, maxRow)

      break;
    case "ArrowDown":
      boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`).classList.remove('active')

      selected.row = Clamp(selected.row + 1, 1, maxRow)

      break;
    case "ArrowRight":
      boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`).classList.remove('active')

      selected.column = Clamp(selected.column + 1, 1, maxCol)

      break;
    case "ArrowLeft":
      boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`).classList.remove('active')

      selected.column = Clamp(selected.column - 1, 1, maxCol)
      break;
    case "Enter":

      boxContainer.querySelector(`.boxGroup:nth-child(${selected.row}) .box:nth-child(${selected.column})`).classList.add('active')

      break;
    default:
      return;
  }

  ActivateSelection(prevBox)
}

const GetActive = () => {
  const active = {}
  boxContainer.querySelectorAll('.boxGroup').forEach((el, index) => {
    el.querySelectorAll('.box').forEach((el2, index2) => {
      if (el2.classList.contains('hover')) {
        active.row = index + 1
        active.column = index2 + 1
      }
    })
  })

  if (active.row) return active

  return undefined
}

ActivateSelection()

window.addEventListener('keydown', OnKeyDown)

boxContainer.querySelectorAll('.boxGroup').forEach((el, index) => {
  el.querySelectorAll('.box').forEach((el2, index2) => {
    el2.addEventListener('click', () => {
      const prev = GetActive()

      selected.row = index + 1
      selected.column = index2 + 1

      ActivateSelection(prev)
    })
  })
})
body,
html {
  margin: 0;
  padding: 0;
}

* {
  box-sizing: border-box;
}

div {
  overflow: hidden;
}

.box {
  background-color: rgb(100, 12, 120);
  height: 100px;
  width: 100px;
  transition: transform .6s;
}

.box-navigation {
  background-color: rgb(100, 12, 120);
  height: 100px;
  width: 100px;
  transition: transform .6s;
}

.box-navigation:hover {
  transform: scale(1.1);
  background-color: rgb(20, 10, 3);
}

.box:hover {
  transform: scale(1.1);
  background-color: rgb(20, 10, 3);
}

.hover {
  background-color: rgb(200, 150, 120);
  transform: scale(.9);
}

.active {
  background-color: rgb(200, 51, 200);
  transform: scale(1.2);
}

.flex-fill {
  flex: 1 1 auto;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">

<div class="container-fluid  h-100 d-flex flex-column bg-secondary text-white">

  <div class="row flex-fill">

    <div class="col-lg-6 row" id="tails" style="background-color: pink;">

      <div class="m-auto  d-flex justify-content-around p-3 boxGroup">
        <div class="box"></div>
        <div class="box "></div>
        <div class="box"></div>
      </div>
      <div class=" m-auto d-flex justify-content-around p-3 boxGroup">
        <div class="box"></div>
        <div class="box hover"></div>
        <div class="box"></div>
      </div>
      <div class=" m-auto d-flex justify-content-around p-3 boxGroup">
        <div class="box"></div>
        <div class="box "></div>
        <div class="box"></div>
      </div>
    </div>

    <div class=" col-lg-6 d-flex justify-content-around " id="text" style="background-color: rgb(156, 51, 69);">
      <div class="box m-auto"></div>
    </div>

  </div>

  <div class="row " id="navigation" style="background-color: rgb(142, 197, 71);">
    <div class=" m-auto d-flex justify-content-around p-3">
      <div class="box-navigation"></div>
      <div class="box-navigation"></div>
      <div class="box-navigation"></div>
    </div>


  </div>

</div>

1 Answers

Although I don't think this is the most effective method, this can be used with any number of rows, columns, or elements.

The size of the elements cannot vary, but I could make a few changes to make it work.

const boxes = document.querySelectorAll(".box");

const elements = {
  current: null,
  last: null
}

focusOnClick();
addKeypressEvent();

function focusOnClick() {
  for (let index = 0; index < boxes.length; index++) {
    boxes[index].addEventListener("click", function(e) {
      if (elements.last) elements.last.classList.toggle('box-active');
      elements.current = e.target;
      e.target.classList.toggle('box-active')
      elements.last = e.target;
    })
  }
}

function addKeypressEvent() {
  document.addEventListener("keyup", function(e) {
    if (elements.current) {
      switch (e.key) {
        case "ArrowUp":
          focusElement(getElementOnTop(elements.current));
          break;
        case "ArrowDown":
          focusElement(getElementOnBottom(elements.current));
          break;
        case "ArrowLeft":
          focusElement(getElementOnLeft(elements.current));
          break;
        case "ArrowRight":
          focusElement(getElementOnRight(elements.current));
          break;
      }
    }
  });
}

function focusElement(element) {
  if (elements.current !== element) {
    elements.current = element;
    elements.current.classList.toggle('box-active');
    elements.last.classList.toggle('box-active');
    elements.last = elements.current;
  }
}

function getElementOnTop(element) {
  for (let i = boxes.length - 1; i !== -1; i--) {
    const isOnTop = boxes[i].offsetTop < element.offsetTop;
    const isAligned = boxes[i].offsetLeft === element.offsetLeft;

    if (isOnTop && isAligned) {
      return boxes[i];
    }
  }
  return element;
}

function getElementOnBottom(element) {
  for (let i = 0; i < boxes.length; i++) {
    const isOnBottom = boxes[i].offsetTop > element.offsetTop;
    const isAligned = boxes[i].offsetLeft === element.offsetLeft;

    if (isOnBottom && isAligned) {
      return boxes[i]
    }
  }
  return element;
}

function getElementOnLeft(element) {
  for (let i = boxes.length - 1; i !== -1; i--) {
    const isOnLeft = boxes[i].offsetLeft < element.offsetLeft;
    const isAligned = boxes[i].offsetTop === element.offsetTop;

    if (isOnLeft && isAligned) {
      return boxes[i];
    } else if (isFirstElementInRow(element)) {
      return getLastElementInPreviousRow(element);
    }
  }
  return element;
}

function getElementOnRight(element) {
  for (let i = 0; i < boxes.length; i++) {
    const isOnRight = boxes[i].offsetLeft > element.offsetLeft;
    const isAligned = boxes[i].offsetTop === element.offsetTop;

    if (isOnRight && isAligned) {
      return boxes[i];
    } else if (isLastElementInRow(element)) {
      return getFirstElementInNextRow(element);
    }

  }
  return element;
}

function isLastElementInRow(element) {
  for (let i = 0, count = 0; i < boxes.length; i++) {
    if (boxes[i].offsetTop === element.offsetTop) count++;
    else if (count > 0) {
      if (element === boxes[i - 1]) {
        return true
      }
      break;
    }
  }
  return false;
}

function isFirstElementInRow(element) {
  for (let i = boxes.length - 1, count = 0; i !== -1; i--) {
    if (boxes[i].offsetTop === element.offsetTop) count++;
    else if (count > 0) {
      if (element === boxes[i + 1]) {
        return true
      }
      break;
    }
  }
  return false;
}

function getFirstElementInNextRow(element) {
  for (let i = 0, count = 0; i < boxes.length; i++) {
    if (boxes[i].offsetTop === element.offsetTop) count++;
    else if (count > 0) return boxes[i];
  }
  return element;
}

function getLastElementInPreviousRow(element) {
  for (let i = boxes.length - 1, count = 0; i !== -1; i--) {
    if (boxes[i].offsetTop === element.offsetTop) count++;
    else if (count > 0) return boxes[i];
  }
  return element;
}
.box {
  width: 50px;
  height: 50px;
  background: gray;
  transition-duration: 100ms;
}

.box-active {
  transform: scale(1.2);
  transition-duration: 100ms;
  background-color:yellow;
}

.box:hover {
  transform: scale(1.2);
  transition-duration: 100ms;
}

#pink-row,
#red-row,
#green-row {
  display: grid;
  padding: 10px;
  grid-template-columns: repeat(5, 1fr);
  justify-items: center;
  gap: 10px;
  width: 400px;
}

#pink-row {
  background-color: pink;
}

#red-row {
  background-color: red;
  grid-template-columns: repeat(1, 1fr);
}

#green-row {
  background-color: green
}
<div id="pink-row">
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
</div>
<div id="red-row">
  <span class="box"></span>
</div>
<div id="green-row">
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
  <span class="box"></span>
</div>

Related