Skip to content

[css-anchor-1] Transitioning when the anchor element with a given name changes #8181

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

Closed
tabatkins opened this issue Dec 2, 2022 · 10 comments

Comments

@tabatkins
Copy link
Member

It's pretty obvious that if an element changes from top: anchor(--foo top); to top: anchor(--foo bottom), that's a computed-value change that will trigger a transition on 'top'.

But what happens when the value is top: anchor(--foo top); the whole time, but you change which element has anchor-name: --foo;? @jh3y has an example of this using the popover API, where they change the popover attribute, altering which element is used as the "implicit anchor element" without actually touching the CSS. They'd like the abspos to smoothly transition to its new position.

I think this gets solved appropriately with the exact same solution as #8180 - if the tree-scoped reference is resolved as part of the computed value, then having it change underneath you counts as a computed-value change, thus triggering a transition, and the start and end values are (non-textually) distinct, referring to the old and new anchor, so you can meaningfully transition between them.

@jh3y
Copy link
Contributor

jh3y commented Dec 5, 2022

Yep – Transitioning the inset properties or being able to use those values in calc to generate transform values would be super useful. The demo in question is this one 👇

working-transitions.mp4

It shows a use case where you might want an element to follow user interaction. Not one I'd throw into production anytime soon 😅

But it works using :has() to update and --active-anchor custom property (this did work in ~108).

:root {
  --active-anchor: --email;
  --active-left: anchor(var(--active-anchor) right);
  --active-top: anchor(var(--active-anchor) center);
}

:root:has(.form-group:focus-within) {
  --anchor-scale: 1;
  --anchor-transition: top 0.2s, left 0.2s, transform 0.2s;
}

#password {
  anchor-name: --password;
}

:root:has(#password) {
  --active-anchor: --password;
}

A combination of implicit anchor and transition could also be useful in a variety of tooltip/overlay scenarios too. Even those type trigger UI where like on here when you type # to get the dropdown (What is the technical name for that pattern? 😅 )

UPDATE::
Forgot to mention the original approach I attempted to make this work. That would involve being able to use anchor values as a reference. I thought something like this would be permitted.

form {
  --anchor-one: anchor(--one top);
  --anchor-two: anchor(--two top);
}
.el { top: var(--anchor-one); }
form:has(#two:checked) el { translate: 0 calc(var(--anchor-two) - var(--anchor-one)); }

It would look a little like that where you could use the value in a transform and use calc with the anchor values to work out the translation.

@kizu
Copy link
Member

kizu commented Mar 15, 2023

Disclaimer and additional links

I'm submitting my feedback following my experiments with the current implementation of anchor positioning in Chrome Canary.

I wrote an article about my experiments, but decided to fill most of my feedback as separate issues here.

A quick summary of related links:

I'm using this issue instead of creating a new one in order not to duplicate things. The issue topic that I wanted to give — “[css-anchor-1] Transitions when moving the anchor-name”.

I did experiment a bit with transitions, and found the same case that works — having multiple elements with different anchor-names, then using a CSS variable for the anchor() and dynamically adjusting the value of this variable.

The experiment in question: https://kizu.dev/anchor-positioning-experiments/#transitions

In my case, initially I wanted to instead change to which element the static anchor-name is applied — so the anchor() would not (necessarily) be a CSS variable, but at any given moment different elements could become the target via moving the “anchor-name” itself from element to an element.

This way the case for my transition would've been significantly simplified.

Here is a CodePen with just the reproducible cases which work and which does not: https://codepen.io/kizu/pen/RwYMmmp

@kizu
Copy link
Member

kizu commented Mar 26, 2023

I would want to provide another use-case — quite an old experiment by @NV, where it could be possible to have a transition for the focus-ring between elements — https://n12v.com/focus-transition/

If the position change that correspond to the change in focus for the :focus-visible { anchor-name: --focused-element; } could be animated, then this would be trivial to implement.

@andruud
Copy link
Member

andruud commented Nov 14, 2023

I think this gets solved appropriately with the exact same solution as #8180 - if the tree-scoped reference is resolved as part of the computed value, then having it change underneath you counts as a computed-value change, thus triggering a transition, and the start and end values are (non-textually) distinct, referring to the old and new anchor, so you can meaningfully transition between them.

In other words, something like a tree-scoped name, but instead of only capturing the tree-scope, it now also captures the element it's referencing? This means intermediate states would appear to interpolate to and from the same value, when serialized, right?

@tabatkins Have we considered resolving anchor() computed-value time instead? (Similar to how cq* units have layout dependencies, but also resolve computed-value time and can trigger transitions).

@andruud
Copy link
Member

andruud commented Nov 15, 2023

Since my previous post raised some questions yesterday (elsewhere), I'll add a more detailed explanation, and some more thoughts on this.


Container relative units (cq*) resolve at computed-value time, even though they require layout information to resolve. Example:

<style>
  #container {
    container: --c / size;
    width: 100px;
    height: 80px;
  }
  #target {
    width: 50cqw;
  }
</style>
<body>
  <div id=container>
    <div id=mid>
      <div id=target>
      </div>
    </div>
  </div>
</body>

During the regular style recalc process, after computing the style for #container and figuring out that it's a size container, we skip computing the style for #container's descendants. So during this process, "computed-value time" has happened for #container, but not for #mid nor for #target.

Then, when layout reaches #container, we immediately resume style recalc for #container's descendants. Now armed with the knowledge of #container's actual size, we're able to resolve (compute) cq* units into what they're supposed to be. So for #container's descendants, "computed-value time" happens during layout. (Hence the term "interleaved style and layout" which you may or may not have encountered).


The above works, because at the time we finally end up resolving cq*, we know the size of the container. (And thanks to containment, we also know that it's not going to move around).

The same appears to be true for anchor(), since we have the requirement that the layout of the anchor must be understood first. However, the mental model doesn't quite work out for anchor(), because there's no ancestral "container" which marks the interleaving point. In order to detect that anchor() is used on some element, "computed-value time" would need to already have happened, and then we'd need to have a second round of "computed-value time"? It's (even) less "clean" than container queries.

The new path we're on for @position-fallback also requires the same uncleanliness: we must know the computed value of position-fallback before attempting the associated @try styles to eventually figure out the real computed style.

Doing multiple rounds isn't a problem for the implemenation, but figuring out a decent mental model (spec) for this might require some thought ...

@kizu
Copy link
Member

kizu commented Nov 15, 2023

Would having something like an anchor-scope (which we'd have, see #9045 (comment)) help in some way? I can imagine that we could make it a requirement that an explicitly scoped anchor's name change could be transitioned, and with the element with that explicit scope we get this “ancestral container”?

@andruud
Copy link
Member

andruud commented Nov 15, 2023

@kizu I don't think so, because in that case the "ancestral container" (i.e. element holding anchor-scope) wouldn't be the thing we're actually querying. When reaching layout of the anchor-scope-element, we still know nothing about the layout of the actual anchor.

@mfreed7
Copy link
Contributor

mfreed7 commented Feb 26, 2024

Should be addressed by the current draft spec.

@mfreed7 mfreed7 closed this as completed Feb 26, 2024
@xiaochengh
Copy link
Contributor

@mfreed7 Is it really addressed? I see it's still an open issue in the spec.

@chrishtr chrishtr reopened this Feb 27, 2024
@mfreed7
Copy link
Contributor

mfreed7 commented Feb 27, 2024

@mfreed7 Is it really addressed? I see it's still an open issue in the spec.

Thanks for raising this! You're right that this still needs work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Friday Afternoon
Development

No branches or pull requests

8 participants