Как синхронно задать класс порядковым элементам при наведении?
Есть симмитричных два блока, внутри них - много span, каждый - с новой строки. Как сделать, чтобы при наведении на любой из span задавался класс hovered ему и симметричному ему span?
.col {
background: #e5e5e5;
width: 45%;
display: inline-block;
}
span {
display: block;
}
.hovered {
background: red;
}
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
<div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
Ответы (5 шт):
Но это если равное кол-во элементов. По хорошему требуется оптимизация - сопоставление блока 1 с блоком 2. Ниже очень простой вариант с поиском текущего индекса элемента.
const divs = [...document.querySelectorAll('div')]
for (const div of divs) {
div.addEventListener('mouseover', mouseover)
}
const storage = []
function resetClass() {
for (const span of storage.splice(0)) {
span.classList.remove('hovered')
}
}
function addClass(spans) {
resetClass()
for (const span of spans) {
span.classList.add('hovered')
storage.push(span)
}
}
function mouseover(e) {
const target = e.target
if (target.tagName !== 'SPAN' || storage.includes(target)) {
return
}
const div1 = e.currentTarget
const div2 = divs.find((d) => d !== div1)
const index = [...div1.children].findIndex((s) => s === target)
const target2 = div2.children[index]
addClass([target, target2])
}
.col {
background: #e5e5e5;
width: 45%;
display: inline-block;
}
span {
display: block;
}
.hovered {
background: red;
}
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
<div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
UPDATE:
- Сопоставляем все родственные элементы. Теперь при mouseover не надо искать индексы.
- MutationObserver на случай изменения блоков.
const containers = [...document.querySelectorAll('.col')]
class SpanWeakMap extends WeakMap {
addItems (...items) {
items.forEach((e) => this.set(e, items))
}
classListAdd (e, ...className) {
this.get(e)?.forEach((item) => item.classList.add(...className))
}
classListRemove (e, ...className) {
this.get(e)?.forEach((item) => item.classList.remove(...className))
}
}
const spanMap = new SpanWeakMap(/* span:[left.span, right.span, ...], ... */)
// Сопоставление - каждый элемент будет ссылаться на все родственные
function collect () {
const cs = containers.map((col) => [...col.querySelectorAll('span')])
cs[0].forEach((_, i) =>
spanMap.addItems(...cs.map((_, ic) => cs[ic][i])))
}
collect()
// Наблюдаем за обновлением коллекций - добавление/удаление элементов(не обязательо если <div class=".col"> не меняется
const observe = (() => {
let tid = null
const update = () => {
clearTimeout(tid)
tid = setTimeout(collect, 100)
}
return (con) => new MutationObserver(update).observe(con, { childList: true })
})()
// Навешиваем обработчики на все ".col"
containers.forEach((con) => {
observe(con)
// Можно не проверять на тег SPAN, наш SpanWeakMap сам все проверит
con.addEventListener('mouseover', ({ target }) => spanMap.classListAdd(target, 'hovered', 'color-white'))
con.addEventListener('mouseout', ({ target }) => spanMap.classListRemove(target, 'hovered', 'color-white'))
})
// +++ Только для эксперимента обновления коллекций +++
// Пробуем добавить/удалить элементы каждые 2 сек
void (() => {
const count = 2
const add = () => {
const last = containers[0].querySelector('span:nth-last-child(-n+1)')
const newElements = (num) => Array(count).fill(null).map(() => {
const e = last.cloneNode(true)
e.classList.remove('hovered', 'color-white')
e.textContent = last.textContent.replace(/\d/, num++)
return e
})
const num = Number.parseInt(last.textContent.match(/\d/)[0]) + 1
containers.forEach((col) => col.append(...newElements(num)))
}
const remove = () => {
const lastItems = [...containers[0].querySelectorAll(`span:nth-last-child(-n+${count})`)]
lastItems.forEach((item) => spanMap.get(item)?.forEach((e) => e.remove()))
}
let invert = false
setInterval(() => ((invert = !invert) ? add() : remove()), 2000)
})()
.col {
background: #e5e5e5;
width: 30%;
display: inline-block;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
span {
display: block;
}
.hovered {
background: red;
}
.color-white {
color: white;
}
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
</div>
<div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
</div>
<div class="some-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
</div>
Вот так. Наверно можно как-то элегантнее, но мне пора пиво пить идти :).
const leftCollection = document.querySelectorAll('.left-part > span');
const rightCollection = document.querySelectorAll('.right-part > span');
const toggleClass = (i) => {
leftCollection[i].classList.toggle('hovered');
rightCollection[i].classList.toggle('hovered');
};
leftCollection.forEach((el, i) => {
el.addEventListener('mouseover', () => {
toggleClass(i);
});
el.addEventListener('mouseout', () => {
toggleClass(i);
});
});
rightCollection.forEach((el, i) => {
el.addEventListener('mouseover', () => {
toggleClass(i);
});
el.addEventListener('mouseout', () => {
toggleClass(i);
});
});
.col {
background: #e5e5e5;
width: 45%;
display: inline-block;
}
span {
display: block;
}
.hovered {
background: red;
}
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
<div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
Обработать в любом случае нужно 2 блока, левый и правый. Вот мое решение по js коду.
const blocksLeft = document.querySelectorAll('.left-part span');
const blocksRight = document.querySelectorAll('.right-part span');
const getObjectUantity = (...length) => {
const [blocksLeftLength, blocksRightLength] = length;
const min = blocksLeftLength;
const max = blocksRightLength;
const maxQuantity = max >= min ? max : min;
const minQuantity = max >= min ? min : max;
const uantity = maxQuantity - minQuantity;
return {
maxQuantity,
minQuantity,
uantity,
};
};
const objUantity = getObjectUantity(blocksLeft.length, blocksRight.length);
const addClassHovered = (settings = {}) => {
settings = {
blocks: '',
index: 0,
classCss: 'hovered',
...settings,
};
const { blocks, index, classCss } = settings;
if (objUantity.uantity !== 0) {
return 'hovered не может быть добавлен в вашем случае элементы не равны.';
}
if (objUantity.uantity === 0) {
blocks[index].addEventListener('mouseover', () => {
blocksLeft[index].classList.add(classCss);
blocksRight[index].classList.add(classCss);
});
blocks[index].addEventListener('mouseout', () => {
blocksLeft[index].classList.remove(classCss);
blocksRight[index].classList.remove(classCss);
});
}
};
for (let i = 0; i < objUantity.maxQuantity; i++) {
const index = i;
addClassHovered({ blocks: blocksLeft, index });
addClassHovered({ blocks: blocksRight, index });
}
Лучше немного поменять разметку и использовать css:
section {
display: grid;
grid-gap: 0 8px;
grid-template-columns: 1fr 1fr;
}
span {
background: #e5e5e5;
}
span:hover, span:hover + span {
background: red;
}
<section>
<span>Слово 1</span>
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 5</span>
<span>Слово 6</span>
<span>Слово 6</span>
</section>
Даже не меняя разметку можно обойтись только css. Хотя и не факт, что так лучше.
Если пустота между колонками не нужна, можно сделать так:
section {
position: relative;
width: 90%;
}
section::before {
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: -2;
background: #e5e5e5;
}
.col {
width: 50%;
display: inline-block;
}
span {
display: block;
line-height: 1.25em;
}
span:hover::before {
content: "";
position: absolute;
left: 0;
right: 0;
height: 1.25em;
z-index: -1;
background: red;
}
<section>
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div><div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
</section>
А если всё-таки нужна, то так:
section {
position: relative;
width: calc(90% + 8px);
--colwidth: calc(50% - 4px);
}
.col {
width: var(--colwidth);
display: inline-block;
}
.col::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: inherit;
z-index: -2;
background: #e5e5e5;
}
.left-part {
margin-right: 8px;
}
span {
display: block;
line-height: 1.25em;
}
span:hover::before, span:hover::after {
content: "";
position: absolute;
height: 1.25em;
width: var(--colwidth);
z-index: -1;
background: red;
}
span:hover::before {
left: 0;
}
span:hover::after {
right: 0;
}
<section>
<div class="left-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div><div class="right-part col">
<span>Слово 1</span>
<span>Слово 2</span>
<span>Слово 3</span>
<span>Слово 4</span>
<span>Слово 5</span>
<span>Слово 6</span>
</div>
</section>