Я просматривал различные похожие вопросы, но нигде не нашел реальной помощи по своей проблеме. Есть несколько примеров JQuery, но ни один из них неприменим, так как я хочу делать это только с помощью ванильного JavaScript.
У меня есть виджет рейтинга, который я хочу построить, как будто каждый раз, когда я нажимаю на элемент "p", eventListener добавляет класс "хорошо" ко всем элементам p до и на выбранном, и удаляет класс хорошо для всех, которые идут после того, по которому щелкнули. .
Я попытался выбрать все элементы «p» и перебрать их, используя цикл for, чтобы добавить класс до и после щелчка по элементу, а также оставить его без изменений или удалить после, но где-то я делаю это неправильно, и он добавляет только класс по нажатому элементу не на всех предыдущих.
function rating () {
var paragraph = document.getElementsByTagName('p');
for (var i = 0; i < paragraph.length; i++) {
paragraph[i].addEventListener('click', function(e) {
e.target.className='good';
})
}
}
rating();
Ожидаемый результат состоит в том, что каждый элемент p до щелчка, включая щелчок, должен иметь хороший класс после щелчка, а все те, которые находятся после щелчка, не должны иметь классов.
Фактический результат: только выбранный элемент имеет хороший класс.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Использование массива # forEach, Элемент # nextElementSibling и Элемент # previousElementSibling
Общая логика этого состоит в том, чтобы перебрать все предыдущие и следующие одноуровневые элементы. Для каждого элемента добавляйте или удаляйте класс .good, пока не останется больше братьев и сестер для обработки.
const ps = document.querySelectorAll('p');
ps.forEach(p => {
p.addEventListener("click", function() {
let next = this.nextElementSibling;
let prev = this;
while(prev !== null){
prev.classList.add("good");
prev = prev.previousElementSibling;
}
while(next !== null){
next.classList.remove("good");
next = next.nextElementSibling;
}
})
})p.good {
background-color: red;
}
p.good::after {
content: ".good"
}
p {
background-color: lightgrey;
}<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>Альтернатива:
Использование Array # slice для получения предыдущей и следующей группы p.
const ps = document.querySelectorAll('p');
ps.forEach((p,i,list) => {
p.addEventListener("click", function() {
const arr = [...list];
const previous = arr.slice(0,i+1);
const next = arr.slice(i+1);
previous.forEach(pre=>{
pre.classList.add("good");
})
next.forEach(nex=>{
nex.classList.remove("good");
});
})
})p.good {
background-color: red;
}
p.good::after {
content: ".good"
}
p {
background-color: lightgrey;
}<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>Ключевой ингредиент - использовать Node.compareDocumentPosition(), чтобы узнать, предшествует ли элемент другому элементу или следует за ним:
var paragraphs;
function handleParagraphClick(e) {
this.classList.add('good');
paragraphs.forEach((paragraph) => {
if (paragraph === this) {
return;
}
const bitmask = this.compareDocumentPosition(paragraph);
if (bitmask & Node.DOCUMENT_POSITION_FOLLOWING) {
paragraph.classList.remove('good');
}
if (bitmask & Node.DOCUMENT_POSITION_PRECEDING) {
paragraph.classList.add('good');
}
});
}
function setup() {
paragraphs = [...document.getElementsByTagName('p')];
paragraphs.forEach((paragraph) => {
paragraph.addEventListener('click', handleParagraphClick);
});
}
setup();#rating { display: flex; }
p { font-size: 32px; cursor: default; }
p:hover { background-color: #f0f0f0; }
.good { color: orange; }<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>`Спасибо тоже @Jeffrey Westerkampf, ваше решение тоже было хорошим, но, честно говоря, я никогда раньше не использовал Node.compareDocumentPosition (), это интересная перспектива, а также решает мою проблему.
Спасибо @Srki, это действительно интересная перспектива. Я честно думаю, что ответ kemicofa лучше подходит для вашего случая. Однако мое решение продолжит работать, если вы, скажем, оберните каждый из своих элементов p в другой элемент.
function setup() {
var paragraph = document.querySelectorAll("p");
for (p of paragraph) {
p.onclick = (event) => {
let index = Array.from(paragraph).indexOf(event.target);
[].forEach.call(paragraph, function(el) {
el.classList.remove("good");
});
for (let i = 0; i <= index; i++) {
paragraph[i].classList.add("good");
}
}
}
}
//Example case
document.body.innerHTML = `
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>`;
setup();Спасибо @westdabestdb, ваше решение было интересным, но мне не хватает полного удаления класса из элемента p. Ваше решение просто удаляет имя класса и оставляет его с <p class> вместо <p> при удалении класса good. Что не совсем неправильно и работает.
Большое спасибо, я пробовал какой-то подход с циклом
forEach, но обнаружил, что добавляю классы для всех элементов p. Это решает проблему, как и ES6, так что @kemicofa, молодец, дружище.