Использование вложенного setTimeout для создания сортировки анимированного выделения - PullRequest
3 голосов
/ 03 августа 2020

Я работаю над базовым c визуализатором сортировки с использованием только HTML, CSS и JS, и у меня возникла проблема с аспектом анимации. Чтобы инициализировать массив, я генерирую случайные числа в определенном диапазоне и помещаю их в массив. Затем, основываясь на размерах веб-страницы, я создаю блоки div для каждого элемента и соответственно задаю каждому из них высоту и ширину и добавляю каждый в свой блок div «bar-container», находящийся в данный момент в dom.

function renderVisualizer() {
  var barContainer = document.getElementById("bar-container");

  //Empties bar-container div
  while (barContainer.hasChildNodes()) {
    barContainer.removeChild(barContainer.lastChild);
  }

  var heightMult = barContainer.offsetHeight / max_element;
  var temp = barContainer.offsetWidth / array.length;
  var barWidth = temp * 0.9;
  var margin = temp * 0.05;
  //Creating array element bars
  for (var i = 0; i < array.length; i++) {
    var arrayBar = document.createElement("div");
    arrayBar.className = "array-bar"
    if (barWidth > 30)
      arrayBar.textContent = array[i];
    //Style
    arrayBar.style.textAlign = "center";
    arrayBar.style.height = array[i] * heightMult + "px";
    arrayBar.style.width = barWidth;
    arrayBar.style.margin = margin;

    barContainer.appendChild(arrayBar);
  }
}

Я написал следующая анимированная сортировка выделения, и она работает хорошо, но единственная «анимированная» часть находится во внешнем for-l oop, и я не выделяю полосы при перемещении по ним.

function selectionSortAnimated() {
  var barContainer = document.getElementById("bar-container");
  var barArr = barContainer.childNodes;
  for (let i = 0; i < barArr.length - 1; i++) {
    let min_idx = i;
    let minNum = parseInt(barArr[i].textContent);
    for (let j = i + 1; j < barArr.length; j++) {
      let jNum = parseInt(barArr[j].textContent, 10);
      if (jNum < minNum) {
        min_idx = j;
        minNum = jNum;
      }
    }
    //setTimeout(() => {   
    barContainer.insertBefore(barArr[i], barArr[min_idx])
    barContainer.insertBefore(barArr[min_idx], barArr[i]);
    //}, i * 500);
  }
}

I Я пытаюсь использовать вложенные вызовы setTimeout, чтобы выделить каждую полосу, когда я прохожу через нее, а затем поменять местами полосы, но у меня возникла проблема. Я использую объект idxContainer для хранения моего минимального индекса, но после каждого запуска innerLoopHelper он оказывается равным i, и, следовательно, своп отсутствует. Я застрял здесь на несколько часов и совершенно сбит с толку.

function selectionSortTest() {
  var barContainer = document.getElementById("bar-container");
  var barArr = barContainer.childNodes;
  outerLoopHelper(0, barArr, barContainer);
  console.log(array);
}

function outerLoopHelper(i, barArr, barContainer) {
  if (i < array.length - 1) {
    setTimeout(() => {
      var idxContainer = {
        idx: i
      };
      innerLoopHelper(i + 1, idxContainer, barArr);
      console.log(idxContainer);
      let minIdx = idxContainer.idx;
      let temp = array[minIdx];
      array[minIdx] = array[i];
      array[i] = temp;

      barContainer.insertBefore(barArr[i], barArr[minIdx])
      barContainer.insertBefore(barArr[minIdx], barArr[i]);
      //console.log("Swapping indices: " + i + " and " + minIdx);
      outerLoopHelper(++i, barArr, barContainer);
    }, 100);
  }
}

function innerLoopHelper(j, idxContainer, barArr) {
  if (j < array.length) {
    setTimeout(() => {
      if (j - 1 >= 0)
        barArr[j - 1].style.backgroundColor = "gray";
      barArr[j].style.backgroundColor = "red";
      if (array[j] < array[idxContainer.idx])
        idxContainer.idx = j;
      innerLoopHelper(++j, idxContainer, barArr);
    }, 100);
  }
}

Я знаю, что это длинный пост, но я просто хотел быть как можно более конкретным c. Большое спасибо за чтение, и мы будем благодарны за любые рекомендации!

Ответы [ 2 ]

1 голос
/ 11 августа 2020

Преобразуйте вашу функцию сортировки в генератор function*, таким образом, вы можете yield при обновлении рендеринга:

const sorter = selectionSortAnimated();
const array = Array.from( { length: 100 }, ()=> Math.round(Math.random()*50));
const max_element = 50;
renderVisualizer();
anim();

// The animation loop
// simply calls itself until our generator function is done
function anim() {
  if( !sorter.next().done ) {
    // schedules callback to before the next screen refresh
    // usually 60FPS, it may vary from one monitor to an other
    requestAnimationFrame( anim );
    // you could also very well use setTimeout( anim, t );
  }
}
// Converted to a generator function
function* selectionSortAnimated() {
  const barContainer = document.getElementById("bar-container");
  const barArr = barContainer.children;
  for (let i = 0; i < barArr.length - 1; i++) {
    let min_idx = i;
    let minNum = parseInt(barArr[i].textContent);
    for (let j = i + 1; j < barArr.length; j++) {
      let jNum = parseInt(barArr[j].textContent, 10);
      if (jNum < minNum) {
        barArr[min_idx].classList.remove( 'selected' );
        min_idx = j;
        minNum = jNum;
        barArr[min_idx].classList.add( 'selected' );
      }
      // highlight
      barArr[j].classList.add( 'checking' );
      yield; // tell the outer world we are paused
      // once we start again
      barArr[j].classList.remove( 'checking' );
    }
    barArr[min_idx].classList.remove( 'selected' );
    barContainer.insertBefore(barArr[i], barArr[min_idx])
    barContainer.insertBefore(barArr[min_idx], barArr[i]);
    // pause here too?
    yield;
  }
}
// same as OP
function renderVisualizer() {
  const barContainer = document.getElementById("bar-container");

  //Empties bar-container div
  while (barContainer.hasChildNodes()) {
    barContainer.removeChild(barContainer.lastChild);
  }

  var heightMult = barContainer.offsetHeight / max_element;
  var temp = barContainer.offsetWidth / array.length;
  var barWidth = temp * 0.9;
  var margin = temp * 0.05;
  //Creating array element bars
  for (var i = 0; i < array.length; i++) {
    var arrayBar = document.createElement("div");
    arrayBar.className = "array-bar"
    if (barWidth > 30)
      arrayBar.textContent = array[i];
    //Style
    arrayBar.style.textAlign = "center";
    arrayBar.style.height = array[i] * heightMult + "px";
    arrayBar.style.width = barWidth;
    arrayBar.style.margin = margin;

    barContainer.appendChild(arrayBar);
  }
}
#bar-container {
  height: 250px;
  white-space: nowrap;
  width: 3500px;
}
.array-bar {
  border: 1px solid;
  width: 30px;
  display: inline-block;
  background-color: #00000022;
}
.checking {
  background-color: green;
}
.selected, .checking.selected {
  background-color: red;
}
<div id="bar-container"></div>
1 голос
/ 11 августа 2020

Итак, я подумал об этом, и это немного сложно, я бы просто сохранил индексы каждого свопа, когда вы делаете сортировку, а затем выполнял бы всю анимацию отдельно, примерно так:

// how many elements we want to sort
const SIZE = 24;

// helper function to get a random number
function getRandomInt() {
  return Math.floor(Math.random() * Math.floor(100));
}

// this will hold all of the swaps of the sort.
let steps = [];

// the data we are going to sort
let data = new Array(SIZE).fill(null).map(getRandomInt);
// and a copy that we'll use for animating, this will simplify
// things since we can just run the sort to get the steps and
// not have to worry about timing yet.
let copy = [...data];

let selectionSort = (arr) => {
    let len = arr.length;
    for (let i = 0; i < len; i++) {
        let min = i;
        for (let j = i + 1; j < len; j++) {
            if (arr[min] > arr[j]) {
                min = j;
            }
        }
        if (min !== i) {
            let tmp = arr[i];
            // save the indexes to swap
            steps.push({i1: i, i2: min});
            arr[i] = arr[min];
            arr[min] = tmp;
        }
    }
    return arr;
}

// sort the data
selectionSort(data);

const container = document.getElementById('container');
let render = (data) => {
    // initial render...
    data.forEach((el, index) => {
        const div = document.createElement('div');
        div.classList.add('item');
        div.id=`i${index}`;
        div.style.left = `${2 + (index * 4)}%`;
        div.style.top = `${(98 - (el * .8))}%`
        div.style.height = `${el * .8}%`
        container.appendChild(div);
    });
}

render(copy);

let el1, el2;
const interval = setInterval(() => {
    // get the next step
    const {i1, i2} = steps.shift();
    if (el1) el1.classList.remove('active');
    if (el2) el2.classList.remove('active');
    el1 = document.getElementById(`i${i1}`);
    el2 = document.getElementById(`i${i2}`);
    el1.classList.add('active');
    el2.classList.add('active');
    [el1.id, el2.id] = [el2.id, el1.id];
    [el1.style.left, el2.style.left] = [el2.style.left, el1.style.left]
    if (!steps.length) {
        clearInterval(interval);
        document.querySelectorAll('.item').forEach((el) => el.classList.add('active'));
    }
}, 1000);
#container {
    border: solid 1px black;
    box-sizing: border-box;
    padding: 20px;
    height: 200px;
    width: 100%;
    background: #EEE;
    position: relative;
}

#container .item {
    position: absolute;
    display: inline-block;
    padding: 0;
    margin: 0;
    width: 3%;
    height: 80%;
    background: #cafdac;
    border: solid 1px black;
    transition: 1s;
}

#container .item.active {
    background: green;
}
<div id="container"></div>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...