-
Notifications
You must be signed in to change notification settings - Fork 677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[css-values] Proposal: add sibling-count() and sibling-index() #4559
Comments
Definitely on board for this. These functions would pave the way for making Splitting.js obsolete and enable some really great animations and effects. Small point for discussion: Should |
Proposal looks good, although it'd be nice if an element could know it's children count too. |
I'm not sure why we have two issues though. Feels like one of them should be closed. |
Maybe there should also be a depth query (with some consideration for what happens in shadow trees). |
This looks like a straight duplicate of #1869, can we close this & copy the discussion over there? That conversation discusses the |
Tab and I felt a new one was due since there were a few issues that alluded to similar functionality but generally had |
Why a function? (With an implementor hat on) it's a bit unfortunate having to do the element instead of node count, as the first is |
What could we use that's not a function @emilio!? Element vs Node, browser internals don't do any distinguishing that you can piggyback on? 0(N), even if the children are fixed and there's no recursion required? tell me more, this sounds interesting 🙂 |
Well, I was thinking there's nothing really preventing you from using an ident or such (functions without arguments are not great), but I guess that would make some properties that can take
At least Gecko keeps the node children count in the parent node (here), so getting the sibling node count is just There's nothing preventing us from tracking element count either, but it grows every node (or at least container node) which is not great. But it seems other engines don't do the same optimization as Gecko, so feel free to take that point as an implementation detail... |
This feels like it should be solved either with predefined counters or with predefined variables that do not have a double-hyphen prefix.
|
I'd been trying to work up a coherent way to do this using I would personally lean towards a new function over a syntax that was a) not a function or b) used Can I suggest that instead of ol[reversed] {
counter-reset: list-item count(children);
} It would be good to leave that option available in the syntax, even if it's not implemented yet. (edit: see also #4181) Idle musing on other uses: .clockhand {
position: absolute;
left: calc(50% + sin(index(sibling) / count(sibling)) * var(--radius));
top: calc(50% + cos(index(sibling) / count(sibling)) * var(--radius));
} |
In #1869 (comment) I showed that if you can get a children count, you can use that to get a sibling count, so if we need an minimum viable product, then I'd rather have a children count than sibling count. I also think "sibling count" is misleading. For example, I am one of two children, but doesn't that mean I have one sibling? Whereas here we're using "sibling count" to mean "sibling count + myself". |
How about |
I don't mind too much between CSS doesn't really allow you to interact with non-element nodes so adding something like |
I like the flexibility of the `index()` and `count()` concept, especially
if a selector could be passed in.
A few possibilities that would be great to see:
* `count(children)` for the number of child elements (akin to `count(“>*”)`)
* `count(“.media”)` for counting the children matching a selector
* `count(siblings)` for getting parent’s element count minus 1.
* `index(siblings)` for getting the element’s index relative to its siblings
* `index(n)` for getting the n value in :nth-child loops for more advanced selecting
Imagine using `count()` to help you determine the number of `grid-columns`!
I realize not all of those would be feasible or possible initially, but having open ended functions could allow their expansion in the future.
|
Does anyone have a use-case where "parent's element count minus 1" is the number you actually want, rather than "parent's element count"? |
@jakearchibald
https://codepen.io/shshaw/pen/LYEEKMQ This can of course be achieved by doing |
This assumes that the index is 0-based. I would prefer it to be 1-based for consistency with nth-child. |
Consistency with nth-child would be good, and avoiding divide-by-0 errors is certainly ideal. Consider me on board for a 1 based `index()` and `count(siblings)` including the element itself.
|
I can immediately see the value of both
For the second idea I've made a plugin to help in the past, though now when I want to use this concept I usually reach for CSS custom properties and a little JS for a cleaner approach. Having the awareness of a tag's index inside |
So I agree with the proposed set of four functions:
I get the concern on precision, but I don't know of another word covering the concept, and "sibling" can be used in this sense (like "I'm one of three siblings"). I think it's the most reasonable name. I do not like being less specific with something like
Node count is just so, so un-useful. ^_^ But also, having these functions disagree with the :nth-child() selectors would be a terrible idea imo.
Hm, yeah, could go either way. I don't plan on extending the functions to more stuff, so I guess keywords could work. I'm slightly wary of adding "keywords that can be used anywhere" because of the potential syntax conflicts (such as animation-name in the 'animation' property); functions avoid that. (We should have ensured that author-defined names were syntactically distinguishable in all cases earlier, but that's a legacy mistake.) If these were usable only in calculations (that is, you have to wrap it in a calc() or other math function), my concern would be alleviated. Tho the suggestion to later extend this to allow a selector, a la the
Definitely 1-indexed, just like :nth-child(). Diverging from :nth-child() would be a terrible mistake.
I think the concept is a lot cleaner to express and understand if it's just "how many children there are"; both "sibling count" and "child count" should agree. Some cases definitely want "siblings other than me", but that's trivial to do in a calc(), and you're probably already using a calc() anyway. (The given example is, to do a division as well.)
Not planning on doing this; it would be super expensive. Use-cases are small enough, as far as I'm aware, that I'm happy to leave that to JS for decorating the element with a value.
This would be a misuse of the var() syntax, and have unfortunate implications regarding parsing; we wouldn't be able to reject a |
I always expected that Anyway, selectors in property values always seem like a bad idea, so Iʼm against a proactive function syntax without parameters, which probably appears natural only to a programmerʼs brain. In pseudo-classes, there are already precedents of something like |
Current state of proposed
Both are possible using /* fig.1: skip hidden <tabs><tab>(1)</tab><tab hidden>(skipped)</tab><tab>(2)</tab></tabs> */
tabs { counter-reset: index; }
tabs > tab:not([hidden])::before { counter-increment: index; content: 'Index: ' counter(index); }
/* fig.2: nesting depth <p><em>(1)<em>(2)<em>(3)</em></em><em>(2)</em></em></p> */
p { counter-reset: depth; }
p em::before { counter-increment: depth; content: 'Depth: ' counter(depth); }
p em::after { counter-increment: depth -1; content: ''; } I think above use cases are quite relevant for problem in question. Unless current proposal will be expanded so that
(yes, using selectors in properties feels super weird), or any other way, I'd rather lean towards #1026. Please pardon unasked intervention and errors; I don't follow all conversations around here, so this had very likely been discussed before; I just felt it should be mentioned here. |
@myfonj While counters can be more flexible, they have some complex inheritance. In order to know the value of a counter in an element, you may have to iterate all the descendants of the previous siblings of the element. And this can be bad for parallelization, see #1026 (comment). So it seems less doable to me. |
Would |
@jonathantneal That would create a circularity, because you should be able to use |
Yeah, the functions would definitely just be counting based on DOM, exactly the same as :nth-child(). |
I appreciate and agree with the responses to my last question. Thank you! Might these related functions be folded into one? Borrowing from an earlier example: /* stagger the animation, start to end */
.animate > g {
animation-delay: calc(this(sibling-index) * 100ms);
}
/* stagger the animation, end to start */
.animate > g {
animation-delay: calc(this(sibling-count) - this(sibling-index) * 100ms);
} I experienced slowness reading Anyway, this is my own experience I’m sharing in case other people feel the same clarity after seeing the above example. |
@kbrilla See #4559 (comment) and #4559 (comment) |
thx, I was looking for |
@kbrilla yep, for the same reason |
I still think it's pretty weird that we're using "sibling count" to mean "number of children the parent has". |
Agree, isn't sibling-count wrong anyway from a language standpoint? Don't we mean smth. like sibling-count including myself / total-sibling-count? |
However, I think it's more useful to include the current element when counting, and |
@Loirooriol And what about |
BTW, IIUC current proposal suggests that And also, is the consensus about mechanism for "skipping" items that it will leverage the
Is it correct? [1] "Index" being term that majority [citation needed] of modern programming languages use for zero-based notations? I have nothing against it, just liked how CSS avoided [2] the list-item-indices-start-at-one ("FORTRAN") camp with that clever naming of |
That's fair. I missed that comment. |
We already bikeshedded the names. And for indexes, CSS always 1-indexes, and specifically |
I'm excited for this to land, I'm starting to see more examples of ol {
&::after {
display: block;
margin-block-start: 1rem;
font-size: 2rem;
}
&:has(> li:last-child:nth-child(1))::after {
content: 'sibling-count() = 1';
}
&:has(> li:last-child:nth-child(2))::after {
content: 'sibling-count() = 2';
}
&:has(> li:last-child:nth-child(3))::after {
content: 'sibling-count() = 3';
}
&:has(> li:last-child:nth-child(4))::after {
content: 'sibling-count() = 4';
}
&:has(> li:last-child:nth-child(5))::after {
content: 'sibling-count() = 5';
}
/* ... */
} |
If I am not mistaken, these functions are currently the only functions that have no argument value definition. From a theoretical point of view, I do not know if an "empty" value definition matches an empty input but from a pratical point of view, an empty value definition is not great, and it seems more appropriate to define these functions as keywords if they are not intented to accept arguments in the future. |
Its argument value definition is not empty. Basically, for a CSS parser that uses syntaxes extracted from the specs, supporting an empty value definition only represents an additional check. So it's not a big deal. With some hindsight, I think they are appropriate if they represent dynamic values, ie. if the styles depending on them are applied when sibling elements are added/removed. |
Well If the consistency with current "draconic" *: such simple mechanism we can already emulate with custom properties and bunch of stupid repetition: |
With the names, are we settled on sibling now? Would repeating the I was thinking something like <div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item" hidden></div>
<div class="item count"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div> .item::after {
content: nth-current(); /* for .count -- 7 */
content: nth-count(); /* for .count -- 13 */
}
.count::after {
content: nth-current(); /* for .count -- 1 */
content: nth-count(); /* for .count -- 1 */
}
.item:not([hidden]) {
content: nth-current(); /* for .count -- 6 */
content: nth-count(); /* for .count -- 12 */
}
.item:not([hidden]) {
/* gradually get more saturated as you reach the last item */
background-color: hsl(9 calc(nth-current() / nth-count() * 100%) 64%);
} |
a correlation problem is that, can we make a pair functions for sibling on one (column or row direction) layout-line, like the "layout-line" is some elements layout on one line after rendered, like some words make a text line. |
@iahu This would basically suffer from the same problems as https://wiki.csswg.org/faq#selectors-that-depend-on-layout For example, you could use |
You are right, this may cause circular problem. |
Problem
Currently we can query a child based on it's child index position or query on children length (with some complex syntax), but we can't use the index or length as values in our styles. Feels like they're known, but not accessible.
so we end up doing stuff like this:
Proposal
2 new functions:
sibling-count()
andsibling-index()
. These should report values based on element count, not node count.sibling-count()
This function returns the total length of sibling elements as a number, similar to
node.childElementCount
(docs) ornode.children.length
but accessible from a sibling. Consider this like a child asking "how many siblings they have".example usage
sibling-index()
This function returns the contextual child index as a number. Similar to the value queried with
nth-child
, this would be a get() call for the contextual child's index in the tree. Consider this like a child asking "what position am I in this family".example usage
All Together
Use Cases
Conclusion
Most other proposals for similar functionality, request access to
counters()
as a unit that can be passed tocalc()
. But I feel that is overloading the counters feature and is rooted in a mindset of leveraging something "close" to what is needed, where what's actually wanted is the child index position for visually reasonable and meaningful UI feedback and presentation.By proposing a solution that doesn't involve counters, I hope to bypass much of the pain points associated with the feature to help unblock the large and ever-growing set of use cases that could leverage these contextual values.
Sources & Chatter
#1869
#1176
#1026
https://www.w3.org/TR/selectors-4/#child-index
https://twitter.com/shshaw/status/1201978228375724032?s=20
https://twitter.com/smfr/status/1202276694230306816?s=20 @smfr
The text was updated successfully, but these errors were encountered: