CSS выбирает всех предыдущих братьев и сестер для звездного рейтинга
Я хочу управлять рейтингом в звездочках, но не могу найти способ выбрать всех предыдущих братьев и сестер при наведении курсора. Такое вообще существует, или я должен использовать javascript?
span {
display:inline-block;
width: 32px;
height: 32px;
background-color:#eee;
}
span:hover {
background-color:red;
}
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
5 ответов
// obtain all spans from DOM
const spans = document.querySelectorAll('span');
// set a variable at global scope as indicator
let flag = false;
// add event listener to each span
spans.forEach((sp, j)=>{
sp.addEventListener('click', ()=>{
// if clicked, then not dismissing the background colour after mouse leave
flag = true;
// reassign all spans back to original grey
spans.forEach(dsp=>{
dsp.style.backgroundColor = '#eee';
});
// assign bg to red of the spans from 0 to clicked index
Array.from(new Array(j+1), (x, i) => i).forEach(ind=>{
spans[ind].style.backgroundColor = 'red';
});
});
// redo if mouse enters
sp.addEventListener('mouseenter', ()=>{
flag = false;
});
// if any span is hovered
sp.addEventListener('mouseover', ()=>{
// reassign all spans back to original grey
spans.forEach(dsp=>{
dsp.style.backgroundColor = '#eee';
});
// assign bg to red of the spans from 0 to clicked index
Array.from(new Array(j+1), (x, i) => i).forEach(ind=>{
spans[ind].style.backgroundColor = 'red';
});
});
// in moseleave, only save the background colour if click happened
sp.addEventListener('mouseleave', ()=>{
if(!flag){
spans.forEach(dsp=>{
dsp.style.backgroundColor = '#eee';
});
}
});
});
span {
display:inline-block;
width: 32px;
height: 32px;
background-color:#eee;
}
span:hover {
background-color:red;
opacity: 0.8;
cursor: pointer;
}
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
" Кажется, я не могу найти способ выбрать всех предыдущих братьев и сестер при наведении курсора "
К сожалению, CSS может нацеливаться только на последующие элементы DOM и, следовательно, выбирать их. К счастью, это, конечно, можно эмулировать с помощью CSS или включить с помощью JavaScript.
Во-первых, подход CSS, который требует либо CSS Grid, либо CSS Flexbox, чтобы настроить порядок элементов на странице:
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
/* to place a 1em gap between items, applicable
to both Grid and Flexbox: */
gap: 1em;
width: 80vw;
margin: 1em auto;
}
div.withFlex {
/* Using flexbox layout: */
display: flex;
/* In the HTML you might have noticed that the '5 star'
element comes before the '1 star'...'4 star' element,
this property reverses the order of the flex-items
(the <span> elements) in the flex-box layout: */
flex-direction: row-reverse;
/* spacing the elements apart, this approach places the
available space (after the element-sizes have been
calculated) between the elements: */
justify-content: space-between;
}
div.withFlex span {
border: 1px solid #000;
flex: 1 1 auto;
}
/* here we use Grid layout: */
div.withGrid {
display: grid;
/* we force the grid-items (the <span> elements) to
flow into columns rather than rows: */
grid-auto-flow: column;
/* here we cause the layout - again - to be reversed,
flowing from right-to-left: */
direction: rtl;
}
div.withGrid span {
border: 1px solid currentcolor;
text-align: left;
}
/* here we select the <span> that the user hovers over,
plus any subsequent siblings, and style them differently;
as the subsequent elements appear - visually - before the
hovered-<span> this gives the illusion that we're selecting
previous elements in the DOM: */
span:hover,
span:hover~span {
color: #f90;
border-color: currentcolor;
}
<div class="withFlex">
<span>5 stars</span>
<span>4 stars</span>
<span>3 stars</span>
<span>2 stars</span>
<span>1 star</span>
</div>
<div class="withGrid">
<span>5 stars</span>
<span>4 stars</span>
<span>3 stars</span>
<span>2 stars</span>
<span>1 star</span>
</div>
В дополнение к вышесказанному, предполагая, что вы хотите, чтобы элементы могли оставаться выбранными - при этом все еще используя CSS и HTML - затем используя некоторые
<input>
и
<label>
также возможны элементы:
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
/* to place a 1em gap between items, applicable
to both Grid and Flexbox: */
gap: 1em;
width: 80vw;
margin: 1em auto;
}
input[type=radio] {
position: absolute;
top: -10000px;
left: -10000px
}
label {
border: 1px solid currentcolor;
cursor: pointer;
}
div.withFlex {
/* Using flexbox layout: */
display: flex;
/* In the HTML you might have noticed that the '5 star'
element comes before the '1 star'...'4 star' element,
this property reverses the order of the flex-items
(the <span> elements) in the flex-box layout: */
flex-direction: row-reverse;
/* spacing the elements apart, this approach places the
available space (after the element-sizes have been
calculated) between the elements: */
justify-content: space-between;
}
div.withFlex label {
flex: 1 1 auto;
}
/* here we use Grid layout: */
div.withGrid {
display: grid;
/* we force the grid-items (the <span> elements) to
flow into columns rather than rows: */
grid-auto-flow: column;
/* here we cause the layout - again - to be reversed,
flowing from right-to-left: */
direction: rtl;
}
div.withGrid label {
direction: ltr;
}
/* here we select the <span> that the user hovers over,
plus any subsequent siblings, and style them differently;
as the subsequent elements appear - visually - before the
hovered-<span> this gives the illusion that we're selecting
previous elements in the DOM: */
label:hover,
label:hover~label {
color: #f90f;
border-color: currentcolor;
}
/* here we select all <label> elements that follow an <input>
of type=radio (using an attribute-selector) which is checked: */
input[type=radio]:checked~label {
color: #f90c;
border-color: currentcolor;
}
<div class="withFlex">
<!-- because we're styling the <label> elements based
on the state (checked/unchecked) of the <input>
elements we have to place the relevant <input>
before the affected <label> in the DOM; which is
why they precede the element that's being styled.
While the :focus-within pseudo-class exists there
is (as yet) no comparable ':checked-within', and
the :has() pseudo-class does not yet (in 2020)
exist; JavaScript could be used but this demo is
to show HTML/CSS methods rather than JS: -->
<input id="withFlexInput5" type="radio" name="rating1" />
<label for="withFlexInput5">
5 stars
</label>
<input id="withFlexInput4" type="radio" name="rating1" />
<label for="withFlexInput4">
4 stars
</label>
<input id="withFlexInput3" type="radio" name="rating1" />
<label for="withFlexInput3">
3 stars
</label>
<input id="withFlexInput2" type="radio" name="rating1" />
<label for="withFlexInput2">
2 stars
</label>
<input id="withFlexInput1" type="radio" name="rating1" />
<label for="withFlexInput1">
1 star
</label>
</div>
<div class="withGrid">
<input id="withGridInput5" type="radio" name="rating2" />
<label for="withGridInput5">
5 stars
</label>
<input id="withGridInput4" type="radio" name="rating2" />
<label for="withGridInput4">
4 stars
</label>
<input id="withGridInput3" type="radio" name="rating2" />
<label for="withGridInput3">
3 stars
</label>
<input id="withGridInput2" type="radio" name="rating2" />
<label for="withGridInput2">
2 stars
</label>
<input id="withGridInput1" type="radio" name="rating2" />
<label for="withGridInput1">
1 stars
</label>
</div>
Здесь есть другие решения CSS, которые используют Flexbox и CSS Grid.
Но если вы счастливы перейти на старую школу, того же эффекта можно добиться с помощью:
float: right;
Рабочий пример:
div {
float: left;
width: 180px;
}
span {
float: right;
display: inline-block;
width: 32px;
height: 32px;
margin-left: 4px;
background-color: #eee;
cursor: pointer;
}
span:hover,
span:hover ~ span {
background-color: red;
}
<div>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
Как предложено в комментариях, один из подходов - стилизовать все, а затем отменить стиль для последующих элементов. Единственный реальный недостаток заключается в том, что если вы находитесь над контейнером div, но между промежутками, вы получите все в красном стиле. Одно из исправлений - поместить все промежутки в одну строку, чтобы между ними не было пробелов.
#parent {
line-height: 0;
display: inline-block;
}
#parent span {
display:inline-block;
width: 32px;
height: 32px;
}
#parent:hover span {
background-color:red;
}
#parent:hover span:hover~span {
background-color:#eee;
}
<span id="parent">
<span></span><span></span><span></span><span></span><span></span>
</span>
Приведенный выше javascript кажется лучше, но если вы ищете решение на чистом CSS, вот странный трюк.
Сначала вам нужно использовать flexbox, чтобы изменить порядок элементов. Затем вы можете использовать селекторы соседей
+
в css, чтобы раскрасить все "предыдущие" элементы (которые на самом деле являются следующими).
Я понимаю, что это немного глупо, может быть, но это решение только для CSS, и было весело попробовать.
И если вам нужно, чтобы он был 6 звезд или 10, его нелегко расширить, потому что вам нужно вставить кучу дополнительных правил CSS. Если вы используете Sass, вы, вероятно, могли бы создать функцию, которая будет генерировать эти правила для вас.
ОБНОВЛЕНИЕ: я видел сообщение @David, и это намного лучше - используя
~
селектор.
.parent {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
}
.parent span {
display: block;
border: 1px solid black;
width: 32px;
height: 32px;
background-color: #eee;
}
span + span {
margin-right: 10px;
}
span:hover {
background-color: red;
}
span:hover + span {
background-color: red;
}
span:hover + span + span {
background-color: red;
}
span:hover + span + span + span {
background-color: red;
}
span:hover + span + span + span + span{
background-color: red;
}
<div class="parent">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>