Последовательное добавление классов
const $cells = document.getElementsByClassName('cell'); // тут блоки которым добавляются классы
const $startButton = document.getElementById('start'); // кнопка активации
$startButton.addEventListener('click', () => {
for (let elem of $cells) {
elem.classList.add('cell__anim');
}
});
Задумка в том, что при нажатии на кнопку, нужно добавить класс cell__anim всем блокам $cells последовательно, т.е. друг за другом, через определенный промежуток времени.
Пытался пропихнуть setInterval внутрь функции, но все классы все равно добавлялись одновременно.
P.S.: чистый JS
Ответы (2 шт):
Через Promise нужно
const $cells = document.getElementsByClassName('cell') // тут блоки которым добавляются классы
const $startButton = document.getElementById('start') // кнопка активации
$startButton.addEventListener('click', async()=> { /// async
for (let elem of $cells){
await new Promise(resolve=>{ /// ждём исполнения функции resolve
setTimeout(()=>{
elem.classList.add('cell__anim')
resolve()
},300) /// Промежуток
})
}
})
.cell__anim{
color: red;
}
<ul>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
<li class="cell">TEST</li>
</ul>
<button id="start">START</button>
Через Promise не нужно, хоть и можно :)
Почему считаю что в данном случае промис не нужен - потому что обертка в объект обещания тут не выполняет какой-либо полезной работы... То есть, обертка в промис особо сильно не вредит в любом случае, но эта конкретная задача вполне успешно решается и без такой обертки:
generateElements();
const $cells = document.querySelectorAll('.cell');
const $startButton = document.getElementById('start');
$startButton.addEventListener('click', () => {
$cells.forEach((cell, idx) => setTimeout(() => {
cell.classList.add('cell__anim');
}, 100 * idx));
}, { once: true });
function generateElements() {
document.body.insertAdjacentHTML('beforeend',
`<section>
${Array.from({ length: 50 }, (_, i) => `<div class="cell">${i + 1}</div>`).join('')}
</section>`
);
}
section { display: flex; flex-wrap: wrap; margin: 1rem 0 0; padding: 1px; }
.cell { flex: 0 0 1.2em; line-height: 1.2; margin: -1px 0 0 -1px; padding: 0.5em; text-align: center; border: 1px solid #ccc; transition: background-color 0.3s ease; }
.cell.cell__anim { background-color: #ded; }
<button id="start">Start</button>
Тут просто устанавливается отдельный таймаут для каждого элемента, с последовательным увеличением значения задержки.
А вот и одним setInterval'ом:
generateElements();
const $cells = document.querySelectorAll('.cell');
const $startButton = document.getElementById('start');
$startButton.addEventListener('click', () => {
let curIdx = -1;
const interval = setInterval(() => {
const cell = $cells[++curIdx];
if (!cell) return void(clearInterval(interval));
cell.classList.add('cell__anim');
}, 100);
}, { once: true });
function generateElements() {
document.body.insertAdjacentHTML('beforeend',
`<section>
${Array.from({ length: 50 }, (_, i) => `<div class="cell">${i + 1}</div>`).join('')}
</section>`
);
}
section { display: flex; flex-wrap: wrap; margin: 1rem 0 0; padding: 1px; }
.cell { flex: 0 0 1.2em; line-height: 1.2; margin: -1px 0 0 -1px; padding: 0.5em; text-align: center; border: 1px solid #ccc; transition: background-color 0.3s ease; }
.cell.cell__anim { background-color: #ded; }
<button id="start">Start</button>
На каждой итерации последовательно увеличиваем индекс элемента в коллекции (пока не выйдем за ее границы) и добавляем класс элементу по текущему индексу.
В обоих случаях используется метод querySelectorAll, который возвращает коллекцию элементов в порядке их следования в документе.
Использование опции once метода addEventListener просто для упрощения - так слушатель будет автоматически удален после первого события (т.е., с этой опцией не нужно самостоятельно удалять слушатель или использовать переменную-флажок "уже запущено").
Бонусное решение - вариант применения промисов, который считаю единственно оправданным в данном случае:
generateElements();
const $cells = document.querySelectorAll('.cell');
const $startButton = document.getElementById('start');
const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
$startButton.addEventListener('click', async () => {
for (const cell of $cells) {
cell.classList.add('cell__anim');
await sleep(100);
}
}, { once: true });
function generateElements() {
document.body.insertAdjacentHTML('beforeend',
`<section>
${Array.from({ length: 50 }, (_, i) => `<div class="cell">${i + 1}</div>`).join('')}
</section>`
);
}
section { display: flex; flex-wrap: wrap; margin: 1rem 0 0; padding: 1px; }
.cell { flex: 0 0 1.2em; line-height: 1.2; margin: -1px 0 0 -1px; padding: 0.5em; text-align: center; border: 1px solid #ccc; transition: background-color 0.3s ease; }
.cell.cell__anim { background-color: #ded; }
<button id="start">Start</button>
Плюс в значительном улучшении читаемости кода, а минус в создании лишних объектов. Оправдано ли это - вопрос личных предпочтений и решаемых задач... я использую "опциональную" промисификацию только когда одновременно выполняются три условия:
- если присутствуют подзадачи где накладные расходы на нее несущественны (с малым кол-вом итераций);
- если обертка в промис является достаточно общей, generic (когда проект большой и можно переиспользовать промисифицированный код в нескольких подзадачах, не изменяя его);
- когда это оправдано реальной практической пользой (не для красоты и не для "тимлид, посмотри, я умею в промисы", а для пользы дела).