|
| 1 | +# Explainer: Tree counting functions - sibling-index() and sibling-count() |
| 2 | + |
| 3 | +## Authors |
| 4 | + |
| 5 | +- Rune Lillesveen ([@lilles](https://github.com/lilles)), Google |
| 6 | + |
| 7 | +## Participate |
| 8 | + |
| 9 | +- https://github.com/w3c/csswg-drafts/issues with prefix `[css-values-5]` |
| 10 | + |
| 11 | +## The Problem |
| 12 | + |
| 13 | +Authors have been asking for a convenient way of styling elements based on |
| 14 | +their DOM position among their siblings as well as the total number of siblings. |
| 15 | + |
| 16 | +Example use cases for this are staggered animations setting the [animation- or |
| 17 | +transition-delay](https://chriscoyier.net/2023/11/29/element-indexes/) based on |
| 18 | +the sibling position, and [building a radial menu](https://una.im/radial-menu/). |
| 19 | + |
| 20 | +It is possible to do this today through custom properties and exhaustingly |
| 21 | +listing all possible number of sibling through `:nth-child()` and |
| 22 | +`:has(:nth-child())` rules for sibling index and sibling count respectively. |
| 23 | +This trick is described on [css-tricks.com](https://css-tricks.com/how-to-wait-for-the-sibling-count-and-sibling-index-functions/#aa-rubbing-two-sticks-together) |
| 24 | + |
| 25 | +## The Solution |
| 26 | + |
| 27 | +The CSS Values and Units Module Level 5 introduces [tree counting functions](https://drafts.csswg.org/css-values-5/#tree-counting), |
| 28 | +specifically `sibling-index()` and `sibling-count()`. These functions can be |
| 29 | +used as `<integer>` values directly or combined with other values in `calc()` |
| 30 | +expressions. The `sibling-index()` and `sibling-count()` functions evaluate to |
| 31 | +integers at computed value time based on the flat tree position of the element. |
| 32 | + |
| 33 | +## Demo |
| 34 | + |
| 35 | +Here is a demo that combines using `sibling-index()` and `sibling-count()` for |
| 36 | +both transform and staggered transitions. The items are distributed in a half- |
| 37 | +circle, the half-circle radius adapts to the number of elements, and the fan- |
| 38 | +out transition that spreads the elements is delayed for each element based on |
| 39 | +its sibling position. |
| 40 | + |
| 41 | +```html |
| 42 | +<!DOCTYPE html> |
| 43 | +<style> |
| 44 | + body { margin: 0; } |
| 45 | + :root { height: 100vh; } |
| 46 | + .item { |
| 47 | + width: 50px; |
| 48 | + height: 50px; |
| 49 | + background: teal; |
| 50 | + position: absolute; |
| 51 | + inset: 0; |
| 52 | + bottom: 20px; |
| 53 | + place-self: center; |
| 54 | + border-radius: 50%; |
| 55 | + transition: transform ease-out 0.5s; |
| 56 | + transition-delay: calc(sibling-index() * 50ms); |
| 57 | + --angle: calc(180.0deg * ((sibling-index() - 1) / (sibling-count() - 1))); |
| 58 | + transform: rotate(var(--angle)) translate(0); |
| 59 | + } |
| 60 | + :root:hover .item { |
| 61 | + transform: rotate(var(--angle)) translate(calc(-20px * sibling-count())); |
| 62 | + } |
| 63 | +</style> |
| 64 | +<div class="item"></div> |
| 65 | +<div class="item"></div> |
| 66 | +<div class="item"></div> |
| 67 | +<div class="item"></div> |
| 68 | +<div class="item"></div> |
| 69 | +<div class="item"></div> |
| 70 | +<div class="item"></div> |
| 71 | +<div class="item"></div> |
| 72 | +``` |
| 73 | + |
| 74 | +## Resources |
| 75 | + |
| 76 | +- [CSS Values 5 Specification](https://drafts.csswg.org/css-values-5/#tree-counting) |
| 77 | +- [How to Wait for the sibling-count() and sibling-index() Functions](https://css-tricks.com/how-to-wait-for-the-sibling-count-and-sibling-index-functions/) |
| 78 | +- [Element Indexes](https://chriscoyier.net/2023/11/29/element-indexes/) |
| 79 | +- [Possible Future CSS: Tree-Counting Functions and Random Values](https://kizu.dev/tree-counting-and-random/) |
| 80 | +- [Building a no-JS radial menu with CSS trigonometry, popover, and anchor positioning](https://una.im/radial-menu/) |
0 commit comments