Javascript & CSS - opening menu has glitchy transition

Viewed 302

I have some code that glitches initially when menus are expanded on top of each other.

If you select option two in the first menu, the second option appears. If you then go to open the first menu, you will see it glitches as it opens - there is almost a shutter-like delay. Maybe it has something to do with the z index setting, I'm not sure?

In firefox and chrome, there is no (apparent) glitch. In safari, the snippet below, there is a glitch.

Why is it glitching?

const selected = document.querySelectorAll(".selected");
const optionsContainer = document.querySelectorAll(".options-container");

for (let i = 0; i < selected.length; i++) {
  selected[i].addEventListener("click", () => {
    optionsContainer[i].classList.toggle("open");
    selected[i].classList.toggle("open");

    for (let j = 0; j < selected.length; j++) {

      if (i != j && selected[j].classList.contains("open")) {
        optionsContainer[j].classList.toggle("open");
        selected[j].classList.toggle("open");
      }
    }
  });
}

for (let i = 0; i < optionsContainer.length; i++) {
  let optionsList = optionsContainer[i].querySelectorAll(".options");

  for (let j = 0; j < optionsList.length; j++) {
    optionsList.forEach(o => {
      o.addEventListener("click", () => {
        selected[i].innerHTML = o.querySelector("label").innerHTML;
        optionsContainer[i].classList.remove("open");
        selected[i].classList.remove("open");
        if (document.getElementById("level").innerText.indexOf('one') === -1) {
          document.getElementById("tier").style.display = "grid";
        } else {
          document.getElementById("tier").style.display = "none";
        }
      });
    });
  }
}
.filter-filterbox-row {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "question select-box";
  padding: 5px 5px;
  margin-top: -2px;
}

.filter-filterbox-row .question {
  grid-template-areas: "question";
  width: 100px;
  padding-top: 5px;
  padding-bottom: 10px;
  padding-left: 15px;
  text-align: right;
}

.filter-filterbox-row .select-box {
  margin-left: -100px;
  grid-template-areas: "select-box";
}

.select-box {
  width: 200px;
}

.select-box .options-container {
  background: #fff;
  width: 200px;
  display: none;
  transition: all 0.4s;
  position: absolute;
  max-height: 240px;
  border: 1px solid #253e5c;
  border-radius: 0 0 4.5px 4.5px;
}

.selected {
  position: relative;
  border: 1px solid #cdcccc;
  border-radius: 4.5px;
  padding: 4px 15px;
  cursor: pointer;
  transition: all 0.4s;
}

.selected.open {
  border-bottom: none;
  border-radius: 4.5px 4.5px 0 0;
  transition: all 0.4s;
  border-color: #253e5c
}

.selected::after {
  content: "";
  background: url(media/dropdown-black.png);
  background-size: contain;
  background-repeat: no-repeat;
  position: absolute;
  height: 100%;
  width: 12px;
  right: 13px;
  top: 10px;
  transition: all 0.4s;
}

.selected.open::after {
  transform: rotateX(180deg);
  top: -11px;
}

.select-box .options-container.open {
  border-color: #253e5c;
  display: block;
  z-index: 99;
}

.select-box .options-container.open .options:nth-child(n+2) {
  border-top: 1px solid #253e5c;
}

.select-box .options-container .options {
  padding-top: 5px;
  padding-bottom: 5px;
  padding-left: 15px;
}

.select-box .options:hover {
  background: #76bc6b;
  cursor: pointer;
}

.select-box label {
  cursor: pointer;
}

.select-box .options .radio {
  display: none;
}

#tier {
  display: none;
}
<div class="filter-filterbox-row" id="level">
  <div class="question"> Level </div>
  <div class="select-box">
    <div class="selected">
      Select level
    </div>

    <div class="options-container">

      <div class="options">
        <input type="radio" class="radio" id="1" name="level">
        <label for="1">one</label>
      </div>

      <div class="options">
        <input type="radio" class="radio" id="2" name="level">
        <label for="2">two</label>
      </div>
    </div>
  </div>
</div>

<div class="filter-filterbox-row" id="tier">
  <div class="question"> Select tier </div>
  <div class="select-box">
    <div class="selected">
      Select tier
    </div>

    <div class="options-container">

      <div class="options">
        <input type="radio" class="radio" id="bronze" name="tier">
        <label for="bronze">Bronze</label>
      </div>

      <div class="options">
        <input type="radio" class="radio" id="silver" name="tier">
        <label for="silver">silver</label>
      </div>
    </div>
  </div>
</div>

4 Answers

There is a delay setting your z-index within the Safari browser, when doing it with CSS.

Setting it with JavaScript resolves the issue:

document.getElementsByClassName('select-box')[0].style.zIndex = 99;

Snippet:

const selected = document.querySelectorAll(".selected");
const optionsContainer = document.querySelectorAll(".options-container");

for (let i = 0; i < selected.length; i++) {
  selected[i].addEventListener("click", () => {
    optionsContainer[i].classList.toggle("open");
    selected[i].classList.toggle("open");
    document.getElementsByClassName('select-box')[0].style.zIndex = 99;
    for (let j = 0; j < selected.length; j++) {
      if (i != j && selected[j].classList.contains("open")) {
        optionsContainer[j].classList.toggle("open");
        selected[j].classList.toggle("open");
      }
    }
  });
}

for (let i = 0; i < optionsContainer.length; i++) {
  let optionsList = optionsContainer[i].querySelectorAll(".options");

  for (let j = 0; j < optionsList.length; j++) {
    optionsList.forEach(o => {
      o.addEventListener("click", () => {
        selected[i].innerHTML = o.querySelector("label").innerHTML;
        optionsContainer[i].classList.remove("open");
        selected[i].classList.remove("open");
        if (document.getElementById("level").innerText.indexOf('one') === -1) {
          document.getElementById("tier").style.display = "grid";
        } else {
          document.getElementById("tier").style.display = "none";
        }
      });
    });
  }
}
.filter-filterbox-row {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "question select-box";
  padding: 5px 5px;
  margin-top: -2px;
}

.filter-filterbox-row .question {
  grid-template-areas: "question";
  width: 100px;
  padding-top: 5px;
  padding-bottom: 10px;
  padding-left: 15px;
  text-align: right;
}

.filter-filterbox-row .select-box {
  margin-left: -100px;
  grid-template-areas: "select-box";
}

.select-box {
  width: 200px;
}

.select-box .options-container {
  background: #fff;
  width: 200px;
  display: none;
  transition: all 0.4s;
  position: absolute;
  max-height: 240px;
  border: 1px solid #253e5c;
  border-radius: 0 0 4.5px 4.5px;
}

.selected {
  position: relative;
  border: 1px solid #cdcccc;
  border-radius: 4.5px;
  padding: 4px 15px;
  cursor: pointer;
  transition: all 0.4s;
}

.selected.open {
  border-bottom: none;
  border-radius: 4.5px 4.5px 0 0;
  transition: all 0.4s;
  border-color: #253e5c
}

.selected::after {
  content: "";
  background: url(media/dropdown-black.png);
  background-size: contain;
  background-repeat: no-repeat;
  position: absolute;
  height: 100%;
  width: 12px;
  right: 13px;
  top: 10px;
  transition: all 0.4s;
}

.selected.open::after {
  transform: rotateX(180deg);
  top: -11px;
}

.select-box .options-container.open {
  border-color: #253e5c;
  display: block;
  z-index: 99;
}

.select-box .options-container.open .options:nth-child(n+2) {
  border-top: 1px solid #253e5c;
}

.select-box .options-container .options {
  padding-top: 5px;
  padding-bottom: 5px;
  padding-left: 15px;
}

.select-box .options:hover {
  background: #76bc6b;
  cursor: pointer;
}

.select-box label {
  cursor: pointer;
}

.select-box .options .radio {
  display: none;
}

#tier {
  display: none;
}
<div class="filter-filterbox-row" id="level">
  <div class="question"> Level </div>
  <div class="select-box">
    <div class="selected">
      Select level
    </div>

    <div class="options-container">

      <div class="options">
        <input type="radio" class="radio" id="1" name="level">
        <label for="1">one</label>
      </div>

      <div class="options">
        <input type="radio" class="radio" id="2" name="level">
        <label for="2">two</label>
      </div>
    </div>
  </div>
</div>

<div class="filter-filterbox-row" id="tier">
  <div class="question"> Select tier </div>
  <div class="select-box">
    <div class="selected">
      Select tier
    </div>

    <div class="options-container">

      <div class="options">
        <input type="radio" class="radio" id="bronze" name="tier">
        <label for="bronze">Bronze</label>
      </div>

      <div class="options">
        <input type="radio" class="radio" id="silver" name="tier">
        <label for="silver">silver</label>
      </div>
    </div>
  </div>
</div>

Based on your snippet, I think something has to do with transform within the pseudo-element and transition: all 0.4s; on most of the elements:

.selected.open::after {
  transform: rotateX(180deg);
  top: -11px;
}

I have two options to fix this:

Option #1: Add z-index to each of .filter-filterbox-row elements.

.filter-filterbox-row {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "question select-box";
  padding: 5px 5px;
  margin-top: -2px;
  position: relative;  // NEW: Relative position.
  z-index: 0;  // NEW: Default z-index value.
}
// NEW: Set higher z-index for parent dropdown.
.filter-filterbox-row#level {
  z-index: 1;
}

Option #2: Change transform value in .selected::after.

.selected::after {
  content: "";
  background: url(media/dropdown-black.png);
  background-size: contain;
  background-repeat: no-repeat;
  position: absolute;
  height: 100%;
  width: 12px;
  right: 13px;
  top: 10px;
  transition: transform 0.4s;  // UPDATE: Change 'all' to 'transform'.
}

Adding the position: relative; to the class .filter-filterbox-row would fix it immediately for Safari:

.filter-filterbox-row {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "question select-box";
  padding: 5px 5px;
  margin-top: -2px;
  position: relative;
}

position: static; is the default normal flow of the document if not explicitly stated. Since you've declared a z-index: 99 on the child div:

.select-box .options-container.open {
  border-color: #253e5c;
  display: block;
  z-index: 99;
}

...stating the z-index assumes the div is in absolute position, thus we need to declare a position for the parent div which is the .filter-filterbox-row. Furthermore, adding the position: absolute; on the child div is required if you need to state the top, bottom, left or right properties.

Declaring an explicit position for the parent div is one way to prevent different browser behavior glitches such as this. The same practice we always do when we declare a z-index on the child div and state the position as relative, the parent div should be declared as well with the position if relative or absolute for the child div to behave accordingly.

Related