Sytuacja kobiet w IT w 2024 roku
13.08.20194 min
Mate Marschalko

Mate MarschalkoSenior Web UI Development ConsultantNews UK

CSS - wybierz n-ty element z klasą

Poznaj sposoby na wybór i ostylowanie w CSS-ie n-tego elementu z określoną klasą przy użyciu CSS lub JavaScriptu.

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.

<p>Loading...</p>