CSS - wybierz n-ty element z klasą
Niedawno wpadłem w sytuację, w której chciałem wybrać n-ty element na liście z określoną nazwą klasy. Naturalnie, rozpocząłem badania nad odpowiednią sztuczką CSS. Niestety, na dzień dzisiejszy funkcja ta nie istnieje w CSS.
W CSS3 mamy selektor nth-of-type
, który wybiera pewne elementy HTML. Gdy korzystamy z selektora nth-of-type
:
p:nth-of-type(3) {
color: red;
}
z takim znacznikiem:
<section>
<h1>Title</h1>
<p>First paragraph</p>
<p>Second paragraph</p>
<hr>
<p>Third paragraph</p>
</section>
Wybierze to trzeci element <p> w obrębie sekcji. Dzieje się tak dlatego, że CSS wybiera element <p>, który jest trzecim pojawieniem się typu akapitu w obrębie rodzica i pomija inne elementy podczas liczenia.
N-ty element z pewną klasą
To, co naprawdę chciałem zrobić, to wybrać n-te pojawienie się elementu z pewną klasą wewnątrz wspólnego rodzica.
Dla przykładu, spójrzmy na takie znaczniki:
<ul>
<li></h1>
<li class="active"></h1>
<li></h1>
<li></h1>
<li class="active"></h1>
<li></h1>
<li></h1>
<li></h1>
<li class="active"></h1>
</ul>
Wyobraź sobie, że chcesz wybrać drugą pozycję na liście, która ma klasę .active
. Jak wspomniałem wcześniej, nie jest to obecnie możliwe, używając samego CSS.
Byłoby to możliwe, gdyby poniższy selektor działał:
.active ~ .active:not(.active ~ .active ~ .active) {
color: red;
}
W tym selektorze użyliśmy ogólnego selektora rodzeństwa (~). W przypadku A ~ B, oznacza to wybranie elementu B, który przychodzi po A w znaczniku HTML wewnątrz tego samego rodzica. Jedyną różnicą pomiędzy tym a sąsiednim selektorem rodzeństwa (+) jest to, że w przypadku A + B, B musi pojawić się bezpośrednio po A.
Mając to na uwadze, widzimy, że nasz proponowany selektor najpierw wybiera element z klasą .active
, który pojawia się po innym elemencie .active
. Oznacza to zasadniczo drugi .active
. Element. Ale problem z tym problemem polega na tym, że wybierałby wszystkie elementy .active
, począwszy od drugiego pojawienia się. Dlatego wyłączamy elementy .active
występujące po drugim pojawieniu się, używając pseudoklasy :
not
.
Powodem, dla którego to nie działa, jest to, że pseudo klasa :not
nie radzi sobie z kombinatorami. Możesz przekazać tylko pojedynczy element, klasę lub id.
Jeśli naprawdę chcesz to osiągnąć, masz dwie możliwości:
- Dodaj nową klasę dla elementu, który chcesz wyszczególnić.
- Wybierz drugie pojawienie się elementu
.active
za pomocą javascript.
Rozwiązanie w JavaScript
Jeśli zdecydujesz się na użycie JavaScript, możesz użyć następującego kodu.
Wybranie drugiego elementu z klasą active
:
document.getElementsByClassName('active')[1].style.color = 'red';
I z JQuery:
$('.active:eq(1)').css('color', 'red');
CSS4 nth-last-match
W przyszłości, CSS4 wprowadzi taką funkcję dzięki selektorom :nth-match
i :nth-last-match
. Z CSS4 możesz po prostu powiedzieć:
li:nth-match(2 of .active) {
color: red;
}
Scenariusze, z którymi radzi sobie CSS
Jeśli chodzi o wybór elementów z określoną klasą lub identyfikatorem, istnieją pewne scenariusze, w których można użyć prostego CSS. Omówimy dwa z nich.
1. Wybierz WSZYSTKIE wystąpienia elementu z określoną klasą od n-tego pojawienia się.
Na przykład, jeśli wybierzemy drugie pojawienie się, oznacza to, że wszystkie elementy z tą klasą zostaną wybrane, ale tylko od drugiego.
Powyższy scenariusz przetłumaczony na CSS:
.active ~ .active ~ .active {
color: red;
}
Ten selektor może bardzo łatwo wyrosnąć na coś długiego i brzydkiego, wyobraź sobie, że chcesz wybrać wszystko za 12. instancją. W takich sytuacjach warto stworzyć funkcję SASS:
@function selectAllFrom($n, $selector) {
$classes: ();
@for $i from 1 through $n {
$className: if($i == 1, #{$selector}, #{"~ " + $selector});
$classes: join($classes, unquote($className), space);
}
@return $classes;
}
#{selectAllFrom(2, '.active')} {
color: red;
}
2. Dodaj stopniowo style do każdego elementu z określoną klasą
Powiedzmy, że masz listę elementów, a niektóre z nich mają klasę .active
. Teraz wyobraź sobie, że chcesz wygaszać je pojedynczo za pomocą animacji CSS i dodać opóźnienie animacji 0,5s na pierwszym elemencie .active
, 1s na drugim, 1,5s na trzecim itd. Ten scenariusz jest właśnie powodem, dla którego szukałem tego selektora i dlaczego zacząłem badać ten temat.
Styl CSS, który by to osiągnął, to:
.active {
animation-name: fade-in;
animation-duration: 1s;
}
.active ~ .active {
animation-delay: 0.5s;
}
.active ~ .active ~ .active {
animation-delay: 1s;
}
.active ~ .active ~ .active ~ .active {
animation-delay: 1.5s;
}
.active ~ .active ~ .active ~ .active ~ .active {
animation-delay: 2s;
}
Ponownie, napisanie tego wszystkiego jest dość pracochłonne, więc aby zaoszczędzić trochę czasu i utrzymać czystość kodu, tutaj jest mieszanka SASS, aby sobie z tym poradzić:
@mixin delayAnim($items, $selector, $delay) {
@for $i from 1 through $items {
$classes: ();
@for $j from 1 through $i {
$className: if($j == 1, #{$selector}, #{"~ " + $selector});
$classes: join($classes, unquote($className), space);
}
#{$classes} {
animation-delay: 0.5s * $i;
}
}
}
@include delayAnim(4, '.active', 0.5s);
Wniosek
Selektory CSS i SASS są dość potężne, więc jest wiele rzeczy, które możemy zrobić bez JavaScript. Dlatego lubię najpierw wykorzystywać możliwości CSS, a dopiero potem sięgać po rozwiązanie JavaScript.
W tym samym czasie w niektórych sytuacjach po prostu łatwiej jest wybierać elementy z JS i włączać i wyłączać na nich klasy. Jeśli masz pięć elementów .active
w aplikacji i chcesz wybrać drugi z jakiegoś powodu, to jest bardzo prawdopodobne, że ten element jest unikalny pod pewnymi względami, które powinny być reprezentowane przez unikalną klasę semantyczną.
Oryginał tekstu w języku angielskim przeczytasz tutaj.