Reputation: 1815
I got some code where I'm selecting some elements by first child, and then styling the accordingly. I noticed that it behaves quite strangely, and the css is only being applied to the first element. I tried to select them both with JS, and it seems like, although I'm doing the exact same thing, just on another element, it doesn't return the div.
Code:
console.log(document.querySelector(".index-0:first-child")); // returns div
console.log(document.querySelector(".index-1:first-child")); // returns null
.index-0:first-child {
color: green;
}
.index-1:first-child {
color: blue;
/* doesn't work */
}
<div class="index-0">
<p class="message"></p>
<div class="shout">
<span>Message 1</span>
</div>
</div>
<div class="index-1">
<p class="message"></p>
<div class="shout">
<span>Message 2</span>
</div>
</div>
jsfiddle: https://jsfiddle.net/a4k03eny/1/
Upvotes: 0
Views: 152
Reputation: 1074355
You're misunderstanding how CSS selectors work. (You're not alone; this is a very frequent misunderstanding.) Compound selectors like .index-1:first-child
or div.foo
are "AND" operations for their individual parts: .index-1:first-child
means that it matches an element that's got the class index-1
and is the first child of its parent. Your .index-1
element isn't the first child in its parent, it's the second child.
In this case, you'd use .index-1
to style it, without the :first-child
bit.
(If your goal is to style the first child within .index-1
, add a >
(a child combinator):
.index-1 > :first-child {
color: green;
}
...but I don't think that's what you're trying to do. Or you could use a space [a descendant combinator], but that would apply to all elements within .index-1
that were the first child of their parent, which I also suspect isn't what you want to do.)
From you comment:
I want to style the first element that has a new class differently.
You can do that by combining .index-1:first-child
with :not(.index-1) + .index-1
:
.index-1:first-child, :not(.index-1) + .index-1 {
color: blue;
}
That latter uses an adjacent sibling combinator to say "apply the rules to an .index-1
that isn't immediately after an .index-1
):
console.log(document.querySelector(".index-0:first-child, :not(.index-0) + .index-0"));
console.log(document.querySelector(".index-1:first-child, :not(.index-1) + .index-1"));
.index-0:first-child, :not(.index-0) + .index-0 {
color: green;
}
.index-1:first-child, :not(.index-1) + .index-1 {
color: blue;
}
<div class="index-0">
<p class="message"></p>
<div class="shout">
<span>Message 1</span>
</div>
</div>
<div class="index-1">
<p class="message"></p>
<div class="shout">
<span>Message 2</span>
</div>
</div>
Of course, that requires that you style them specifically (rules for .index-0
, .index-1
, etc.). You can't do it generically in CSS, but you could in JavaScript. This looks through all elements with the class index
, picks out their index-N
class name if they have it, and adds highlight
to the first in any given run of them:
var lastClass = null;
document.querySelectorAll(".index").forEach(function(e) {
var m = e.className.match(/(?:^| )(index-\d+)(?: |$)/);
var thisClass = m ? m[1] : null;
if (thisClass != lastClass) {
lastClass = thisClass;
e.classList.add("highlight");
}
});
Example:
var lastClass = null;
document.querySelectorAll(".index").forEach(function(e) {
var m = e.className.match(/(?:^| )(index-\d+)(?: |$)/);
var thisClass = m ? m[1] : null;
if (thisClass != lastClass) {
lastClass = thisClass;
e.classList.add("highlight");
}
});
.highlight {
color: green;
}
<div class="index index-0">
<p class="message"></p>
<div class="shout">
<span>first .index-0</span>
</div>
</div>
<div class="index index-0">
<p class="message"></p>
<div class="shout">
<span>second .index-0 in a row</span>
</div>
</div>
<div class="index index-1">
<p class="message"></p>
<div class="shout">
<span>first .index-1</span>
</div>
</div>
Note: Many browsers now support forEach
on the NodeList returned by querySelectorAll
, but not quite all do. You can polyfill it if missing:
if (typeof NodeList !== "undefined" &&
NodeList.prototype &&
!NodeList.prototype.forEach) {
Object.defineProperty(NodeList.prototype, "forEach", {
value: Array.prototype.forEach
});
}
Upvotes: 2