setTimeout не работает в делегированном событии

Рейтинг: 0Ответов: 1Опубликовано: 17.08.2023

const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('dragstart', () => {
    // Добавление класса перетаскивания к элементу после задержки
    setTimeout(() => item.classList.add('dragging'), 0);
});
// Удаление класса перетаскивания из элемента в событии dragend
 item.addEventListener('dragend', () => item.classList.remove('dragging'));
});
    <ul class="sortable-list">
        <li class="item" data-item="217" draggable="true">Text-1</li>
        <li class="item" data-item="216" draggable="true">Text-2</li>
        <li class="item" data-item="215" draggable="true">Text-3</li>
    </ul>

Данные получаю через json, и уже в JS скрипте вывожу их. Это у меня сортировка списка задач, при перетаскивания списка, должен вешаться класс ".dragging", а когда переместили, то должен удаляться. Читал я про делегирования события, навешал обработчик на предка, но класс почти что не ставиться, он за долю секунда появился и исчезает..... Не могу понять как это побороть, сам код работает, потому что, когда выводил данные через PHP, то я отлавливал, но там не было делегированности!

// работаю через предка
const sortableList = document.querySelector('.sortable-list');

sortableList.addEventListener('click', function () {

const items = document.querySelectorAll('.item');

items.forEach(item => {
    item.addEventListener('dragstart', () => {
        setTimeout(() => item.classList.add('dragging'), 0);
    });

    //item.addEventListener('dragend', () => item.classList.remove('dragging'));
});
});

// также пробывал без предка
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('dragstart', () => {
    // Добавление класса перетаскивания к элементу после задержки
    setTimeout(() => item.classList.add('dragging'), 0);
});
// Удаление класса перетаскивания из элемента в событии dragend
 item.addEventListener('dragend', () => item.classList.remove('dragging'));
});


   // это в обычном простое
    <ul class="sortable-list">
        <li class="item" data-item="217">Text-1</li>
        <li class="item" data-item="216">Text-2</li>
        <li class="item" data-item="215">Text-3</li>
    </ul>

   // это когда я перемещаю, то класс dragging появляется
    <ul class="sortable-list">
        <li class="item" data-item="217">Text-1</li>
        <li class="item" data-item="216">Text-2</li>
        <li class="item dragging" data-item="215">Text-3</li>
    </ul>

   // это уже когда я переместил, то класс dragging удаляется задача перемещена
    <ul class="sortable-list">
        <li class="item" data-item="217">Text-1</li>
        <li class="item" data-item="215">Text-3</li>
        <li class="item" data-item="216">Text-2</li>
    </ul>

Ответы

▲ 0

Раскрою мысль из комментов в ответе.
Тут пример с requestAnimationFrame. С ним чуть больше уверенности, что всё отработает правильно и изменение класса будет происходить в нужный момент. Так код выполняется при следующем рендеринге браузера, что более надёжно и предсказуемо.

document.addEventListener('DOMContentLoaded', function() {
  const items = document.querySelectorAll('.item');
  items.forEach(item => {
    item.addEventListener('dragstart', () => {
      requestAnimationFrame(() => {
        item.classList.add('dragging');
        item.querySelector('.color-indicator').style.backgroundColor = 'red';
      });
    });

    item.addEventListener('dragend', () => {
      requestAnimationFrame(() => {
        item.classList.remove('dragging');
        item.querySelector('.color-indicator').style.backgroundColor = '';
      });
    });
  });
});
.sortable-list {
  list-style: none;
  padding: 0;
}

.item {
  padding: 10px;
  border: 1px solid #ccc;
  margin: 5px;
  cursor: grab;
  user-select: none;
}

.dragging {
  background-color: lightblue;
}

.color-indicator {
  width: 20px;
  height: 20px;
  display: inline-block;
  border-radius: 50%;
  margin-left: 10px;
  transition: background-color 0.3s;
}
<ul class="sortable-list">
  <li class="item" data-item="217" draggable="true">Text-1 <span class="color-indicator"></span></li>
  <li class="item" data-item="216" draggable="true">Text-2 <span class="color-indicator"></span></li>
  <li class="item" data-item="215" draggable="true">Text-3 <span class="color-indicator"></span></li>
</ul>

Для наглядности, когда появляется класс, на элементе списка возникает красный кружок, когда класс удаляется, кружок исчезает. (чтобы не делать это вычурно через alert'ы)

В этом же snippet можно и setTimeout использовать как у вас:

document.addEventListener('DOMContentLoaded', function() {
  const items = document.querySelectorAll('.item');
  items.forEach(item => {
    item.addEventListener('dragstart', () => {
      setTimeout(() => {
        item.classList.add('dragging');
        item.querySelector('.color-indicator').style.backgroundColor = 'red';
      }, 0);
    });

    item.addEventListener('dragend', () => {
      setTimeout(() => {
        item.classList.remove('dragging');
        item.querySelector('.color-indicator').style.backgroundColor = '';
      }, 0);
    });
  });
});

И работает он в рамках snippet'а без проблем, по крайней мере у меня. Но есть нюанс. Если поиграться со значениями setTimeout и поставить не }, 0);, а, допустим, 1000, начинаются баги 😁

Моё предположение, что у вас браузер нулевые значения отрабатывает с глюками, и requestAnimationFrame, возможно, решит вашу проблему, т.к. более надёжен.