Open psylok opened 7 years ago
Relevant section of the spec, which includes an inline issue discussing possible use cases. But getting an issue on GitHub will hopefully provide an easier forum for other people to mention their use cases and/or possible implementation issues.
Hi, ran into a case today where this would have helped, so posting a comment here.
I'm setting up some CSS for an image/logo slider. While I know right now that there are 8 images, I don't know how many there will be in the future (nor do I wish to have to know). I'm using only CSS to animate it, and it would be useful to be able to say "for each one of the items that exist (, increment this property by x." I ended up using sass to do something like this:
@for $i from 1 through 8 {
&:nth-of-type(#{$i}) {
$delay: calc(5s * #{$i});
animation: slideIn 45s linear $delay infinite;
}
}
The problem with this proposal is that counter()
returns a <string>
, but calc()
does not work with strings. For example, counter(id, upper-latin)
might be 'A'
. How exactly is calc()
supposed to know that this means 1
?
So I think we need some way to get the numeric value of a counter, either by adding some parameter to counter()
or a new function. This should be allowed to appear anywhere an integer is expected, including (but not necessarily in) calc()
. I wrote my thoughts in #1871.
JFTR, I would hate to see int()
or str2num()
in CSS.
Yeah, an explicit parsing function is probably bad. No real need for it. But having something that can retrieve a counter value as a number (rather than going to the extra effort of formatting it as a string) would work. (Same as how attr()
has functionality to parse the attr value as a number or dimension.)
Wouldn't allowing counter()
in calc()
either ruin any attempt to parallelize styling, or make us need to delay calculating of every numeric value to used-value time?
@upsuper Fair point, counter values are inherited from the immediately preceding element in document order instead of from the parent element as usual. So maybe it would be better to add a sibling-index()
function as Tab Atkins proposed in https://github.com/w3c/csswg-drafts/issues/1869#issuecomment-340078522; it would be less powerful but I think it would cover most usecases.
However, note that the CSS Lists draft currently allows counter()
anywhere a <string>
is expected, doesn't this have the same problem? If it's possible to use a counter value as a string, it should also be possible to use it as an integer.
However, note that the CSS Lists draft currently allows
counter()
anywhere a<string>
is expected, doesn't this have the same problem? If it's possible to use a counter value as a string, it should also be possible to use it as an integer.
Firstly, there are a lot fewer places where <string>
is accepted, and they are usually relatively less complicated than <length>
and its friends when it comes to layout, so expanding them in used-value time (like what is currently done for content
property) may not be as bad.
Also, allowing counter()
anywhere <string>
is allowed is something new. counter()
functions are listed as independent item of content
property in CSS2. And I'm not aware of any browser who has implemented that anywhere outside content
. That means, its feasibility may also be questioned, and my argument above can potentially be apply to that as well.
Firstly, there are a lot fewer places where
<string>
is accepted, and they are usually relatively less complicated than<length>
and its friends when it comes to layout, so expanding them in used-value time (like what is currently done for content property) may not be as bad.
It may not be as bad, but it could still be very bad. One issue comes to my mind is that <family-name>
can be a <string>
, and font-family
is inherited, so you may really want to make counter()
be expanded in computed-value time (rather than in used-value time like what we do now for content
), otherwise it can lead to some funny behavior. This is some case which seems to be neither useful nor easy to implement, which would be a native consequence if we allow counter()
be in anywhere <string>
is allowed.
I guess we should start this topic in a separate issue, now.
I would really, really love to have counter's value available inside calc()
. The syntax I'd propose could be something like counter-value()
that would return the counter's value as an integer. Other than the use-cases for sibling-index
, this could be used for a lot more cases (not only experimental ones). While I understand that that can be non-trivial to implement, the possibilities it would unlock would be tremendous.
Two more use cases:
animation-delay
z-index
Regarding syntax, I like @tabatkins's proposal for a new formatting argument, if an explicit syntax is required.
Although IMO ideally it would be nice if we could auto cast number-like strings to numbers in calc()
akin to how many programming languages do it (including JS). It's not like strings actually do anything in calc()
, so there's no disambiguation problem.
My sad excuse of a utility without this feature...
...
.count--3 { --count: 3 }
.count--4 { --count: 4 }
.count--5 { --count: 5 }
...
.count > :nth-child(1) { --count-current: 1 }
.count > :nth-child(2) { --count-current: 2 }
.count > :nth-child(3) { --count-current: 3 }
...
.el {
animation-delay: calc((var(--count, 0) - var(--count-current, 0)) * 0.1s);
}
I like this in general and I'd love to see it implemented too. But I'd really, really hate implementing it.
All of the paged media layout engines have the same problem with counters, in particular "page" and "pages", although technically it's any counters incremented in the @page margin areas (still a largely theoretical constraint at the time of writing). The moment you hit one of these you have to consider a second layout pass - it's not always required, but for something like span::after { content: counter(pages upper-roman); }
you really haven't got much choice.
You need the first layout pass to count the pages, or to work out with certainty which page your element is on (that's more of an issue with target-counter(nnn, page)
to be fair). But once you start introducing counters to other properties - say margin-top: calc(counter(pages) * 20px)
- you've introduced a loop: layout depends on values computed from an earlier pass of the layout, and so on.
It's not quite as awful for the "page" counter, or any counter other than "pages". But it's still a little complex: orphans: calc(counter(page) * 20)
may force a page-break, which would change the value of orphans...
So although I'm not exactly against this, I just wanted to flag that it is almost unfeasibly hairy for some cases. Think of the multiple passes required to stabilise layout for ::first-line, except it's a whole document that needs to be stabilised. These would need to be considered if this goes anywhere.
Won’t we get this for free once we have a function to convert between types, the likes of which has been discussed multiple times?
I landed here because I assumed I would be able to do something like this in CSS:
li:nth-child(n) {
background-color: hsl(calc(10 *n), 100%, 70%);
}
working on a calendar like grid, was sure i'd be able to layout overlapping scheduled events by the use of counter-value. js got tons of practices for a conditional index. can't css have 1? (excluding nth-child selectors🤓)
Another use case here:
.item:nth-of-type(n) {
transform: rotate(calc(n*10deg)); /* 10 comes from 360/totalItemsCount */
}
Another use case here:
.item:nth-of-type(n) { transform: rotate(calc(n*10deg)); /* 10 comes from 360/totalItemsCount */ }
Could you elaborate a bit on this? n
cannot be used in calc()
, and it's unclear what it would mean if it could. :nth-of-type(n)
matches everything, so you could remove that pseudo-class entirely with the same result.
If what you need access to is the total count of items, counter-value()
could not help here, as it would be different on each item.
Could you also elaborate on why you are dividing 360deg
by the total number of items? Are you placing them around a circle of something? A screenshot of a sample rendering would help a lot here!
@ShahriarKh Your use case seems better covered by #4559:
.item { transform: rotate(calc(1turn * sibling-index() / sibling-count())) }
With a counter-based approach you would also need #3667 to know the totalItemsCount.
@LeaVerou
Could you elaborate a bit on this?
n
cannot be used incalc()
, and it's unclear ....
Yeah I know this isn't possible. I find this discussion when I was playing with CSS: https://codepen.io/shahriarkh/pen/rNGwQMQ I wanted to place some bars inside the circle like this:
Declaring rotation for every bar isn't a good idea, so I wondered it will be useful if we had something like this:
.bar:nth-of-type(n) {
transform: rotate(calc(n * 36deg))
}
Here's another way this feature could be used : decreasing the size of siblings.
.circles {
counter-reset: n;
}
.circles .circle {
counter-increment: n;
width:calc(100% / counter(n) );
}
Is there any hope to get something like this in CSS one day or is that a task more suited for javascript ?
And I got here to sadly find out this is still open and with yet another use case (I couldn't find it by scanning the ones here): counting DOM depth, which I intented use to indent only one element in nested tags. Like:
body {
counter-reset: tag-depth;
}
* > .indented {
counter-increment: tag-depth;
}
.indented {
margin-left: calc(1.5rem * counter-value(tag-depth);
}
I would totally solve this with recursive adding 1 to a custom property (like a * > .indented { --tag-depth: calc(var(--tag-depth) + 1);}
) but sadly this isn't allowed either. =(
@JJanz Rather than counters, you may have better luck with inherit()
, already approved by CSSWG resolution: https://github.com/w3c/csswg-drafts/issues/2864#issuecomment-816280875
@property --tag-depth {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
.indented {
--tag-depth: calc(1 + inherit(--tag-depth));
margin-left: calc(1.5rem * var(--tag-depth);
}
Note that with counters aren't probably what you want for depth, e.g. the 2nd .indented
would have a counter value of 2:
<div class="indented"></div>
<div class="indented"></div>
Rather than counters, you may have better luck with
inherit()
, already approved by CSSWG resolution: #2864 (comment)
Neat! I couldn't get to it in my research! Thanks!
Note that with counters aren't probably what you want for depth
Yes, I was aware my example could fail somehow (I did see it fail by a quick test writing counter(tag-depth)
on content
property but thought maybe there was a way a through - and I was out of options, anyway), as in fact I made it from an actual code I started in the lines of the second example I gave. First made it when sure it would work and on a train of thought that is exactly what inherit() seems to do but, on no avail, following research brought me to counters and here.
Again, thank you so much for your help! =)
A major styling issue front-end developers and designers experience is when they are tasked with dynamically changing styling of repeated elements. As of writing this is normally done either via swaths of :nth-child()
and/or Javascript.
Permitting the use of counters within calc() can greatly simplify and condense such verbose code.
Let's say we wanted to animate each of the following list items in a cascade.
<ol>
<li>Foo</li>
<li>Bar</li>
<li>Bizz</li>
<li>Buzz</li>
</ol>
Currently it has to be manually like so:
@keyframes reveal { 0% { opacity:0; } }
ol>* { animation: reveal both 3s; }
li:nth-child(1) { animation-delay: 0.5s; }
li:nth-child(2) { animation-delay: 1s; }
li:nth-child(3) { animation-delay: 1.5s; }
li:nth-child(4) { animation-delay: 2s; }
When the number of items or timing changes, each element must be revised.
However, counter values allow code that is:
@keyframes reveal { 0% { opacity:0; } }
ol>* {
animation: reveal both 3s;
animation-delay:calc( counter-value(list-item) / 0.5s );
}
This opens up the world to so many other effects that were limited to JS and annoying manual composition in a clean form... although my creative juices are running low - I bet someone more creative such as @argyleink can come up with many other fun uses.
Perhaps limiting use to the special list-item counter to start with could guarantee some sort of baseline to get the rest sorted? My concern as an end user is that other potential methods (Such as the @property
example shown in a comment above) don't have the clarity that a counter value, or the proposed sibling-index()
have...
//EDIT// Just realized I commented on the sibling-index() proposal last year, whoops. Hope it gains more traction.
I too was sad that this did not work. I had to turn something elegant:
.nav ol li.active ~ .indicator {
transform: translateX(calc(70px * calc(counter(list-item) - 1)));
}
into a whole string of hideous and fragile:
.nav ol li:nth-child(0).active ~ .indicator {
transform: translateX(calc(70px * 0));
}
... cases 1..11 here ...
.nav ol li:nth-child(12).active ~ .indicator {
transform: translateX(calc(70px * 12));
}
If you need another example, this can be useful with overlapping grid items. In this example the user wants item1 to span both columns and item2 to be in the second column for each row. Doing this without counters requires explicit row numbers.
#grid {
display: grid;
grid-template-columns: repeat(2, 20px);
counter-reset: row;
}
.item1 {
counter-increment: row;
grid-column: 1 / span 2;
grid-row: counter(row);
}
.item2 {
grid-column: 2;
grid-row: counter(row);
}
<div id="grid">
<div class="item1">1</div>
<div class="item2">1</div>
<div class="item1">2</div>
<div class="item2">2</div>
</div>
👍 +1 on this! Very hot
👍 +1 on this! Very hot
Hi @mnik01 and welcome to the csswg repo! Just a quick note that since these discussions can get quite long, we tend to avoid posting +1s etc unless we also have something else to say in addition to the expression of support and use reactions instead to simply express support.
Hey all, as the scope of this issue has changed quite a bit from its original concept/spec, it may be worth considering changing the name on the ticket, so it can be found more easily.
I essentially duplicated the more recent evolution of this issue in #8981.
@tabatkins made a good point in my related ticket that using n
to reference the n
from an nth-child
statement wouldn't work well since multiple nth-child
statements can be used on a single selector.
One possibility would be to introduce a second optional argument to nth-child
for a CSS custom property to assign the iterator value to, like this:
element:nth-child(n + 1, --n) {
transition: /* ... */;
--delay: calc(var(--n) * 200ms);
transition-delay: var(--delay);
}
In either case, we should still discuss all the pros/cons/implications of allowing selectors to also be property-declarative in this way, as this would set a very new precedent not before used in CSS afaik.
Another consideration of using a second argument in those pseudo-selector expressions for this is that it would also beg the question, "Would each :nth-child()
second arg value yield a unique value per its expression, or would this iterator value match the nth
matched element?"
element:nth-child(n, --m):nth-child(n + 1, --n) {
/* are `m` and `n` equal?
it's a bit unclear. */
}
Alternatively, should an iterator require a completely different syntax altogether, such as the following example, where the custom property name is used completely outside the expression itself, after the selector and before the style block?
element:nth-child(n):nth-child(n + 1) --n {
transition: /* ... */;
--delay: calc(var(--n) * 200ms);
transition-delay: var(--delay);
}
@brandonmcconnell, custom properties are intentionally left untouched by the language, so if provide this constant as a variable, then it should be done at the property declaration level: for example, some special value or function nth
for the initial value descriptor.
@rthrejheytjyrtj545 That makes sense. In that case, it would probably make the most sense as a <calc-constant>
that needs to be used in a property (standard or custom) with the styles under a selector with an :nth-child()
rule, or similar, which would match the nth
matched element.
Counters can be modified within all the subtree in abitrary ways. If you just want the element index and sibling count, refer to #4559 instead.
@Loirooriol Thanks for pointing me to that issue. Adding my thoughts there. 🙂
It's not possible for counter()
to return a string or integer depending on whether it's inside a string expression or calc()
expression? I just mean, why counter-value()
?
Background: I wanted to initialize a counter using the counter()
of another counter...
Related: Has there been discussion anywhere about permitting @property { inheritance: tree-order; }
or similar? It would be another way to propagate a value per the same rule that counters follow.
@tavin calc(counter(foo, lower-alpha))
makes no sense. Also it's confusing if the same expression can resolve to entirely different types depending on the context.
@Loirooriol we can debate whether counter(foo)
is confusing in a numeric expression, but any idea whether this or even calc(counter-value(foo))
has a real chance of being adopted?
Doesn't seem much likely to happen due to https://github.com/w3c/csswg-drafts/issues/1026#issuecomment-341295647
Doesn't seem much likely to happen due to https://github.com/w3c/csswg-drafts/issues/1026#issuecomment-341295647
Could something akin to setting up a particular scope for a certain counter help with that?
Similar to how we can use timeline-scope
to set up a scope for an animation? So something like a counter-value-scope: foo
on a container switching anything that tries to access this particular counter's value inside to this non-parallel/delayed slower mode? This way nothing would be affected by default, and authors couldn't easily “break” the performance of everything, as they would be required to explicitly state which counter should receive this behavior.
Alternatively maybe some sort of nth-usage inside calc?
foo:nth-child(n) {
animation-delay: calc(n) * 20ms;
}
@tomasdev See #8981, "We cannot make values dependent on what kind of selector they were applied with".
However, in this case you could use animation-delay: calc(sibling-index() * 20ms);
which is already in the spec.
@tomasdev See #8981, "We cannot make values dependent on what kind of selector they were applied with". However, in this case you could use
animation-delay: calc(sibling-index() * 20ms);
which is already in the spec.
Thanks for the update re: sibling-index()
@Loirooriol! This would permit me to achieve my intended use showcased in my 2022 comment above. Hope to see it implemented in the future. When used along with sibling-count()
it could generate a sort of seed based randomness for artistic use (confetti, etc)... although I hope it works on #foo::before{content:''}
elements!
@alystair For randomness, see https://drafts.csswg.org/css-values-5/#randomness
For sibling-*()
on pseudo-elements, see #9573
sibling-index()
is nice but counter()
is more powerful, as it doesn't just count siblings (and can be incremented with any increment). I would much rather see something that works like counter()
but produces a number.
Feature request. It would be nice to be able to use the counter() function inside of calc() function. That would enable new possibilities on layouts.
I copy here the link to a thread which proposed it last august. https://lists.w3.org/Archives/Public/www-style/2016Aug/0073.html