Skip to content

[css-scroll-anchoring] anchoring within contenteditable elements #11748

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

Open
vmpstr opened this issue Feb 19, 2025 · 10 comments
Open

[css-scroll-anchoring] anchoring within contenteditable elements #11748

vmpstr opened this issue Feb 19, 2025 · 10 comments

Comments

@vmpstr
Copy link
Member

vmpstr commented Feb 19, 2025

According to https://drafts.csswg.org/css-scroll-anchoring/#anchor-priority-candidates one of the priority candidates is the DOM Anchor of the focused element if that element supports text entry.

In practice it means that if we have a scrollable contenteditable element, then its the element itself that becomes the scroll anchor. However, there are situations where pages can add or remove content from within the contenteditable subtree (e.g. multiplayer text editor), which results in the scroll position within the contenteditable element to jump (since the element itself is anchored).

I propose we adjust the priority candidate to prioritize the nearest (perhaps the closest preceeding) element to the cursor within a focused contenteditable element. I'm not sure if there's a more general phrasing of this.

/cc @emilio @flackr

@vmpstr
Copy link
Member Author

vmpstr commented Feb 19, 2025

This also help with non-scrolling contenteditable large elements to adjust its ancestor scroll offsets to match roughly the cursor position

@vmpstr
Copy link
Member Author

vmpstr commented Feb 19, 2025

Thinking a bit more about this... We can also adjust the algorithm to say that the focused element is a priority, but we don't directly select priority elements but rather use the priority element as the root for the rest of the scroll anchoring: we start at the focused element, if one exists, and then run the typical scroll anchoring that selects the first element that is fully visible in that subtree

@flackr
Copy link
Contributor

flackr commented Feb 20, 2025

Thinking a bit more about this... We can also adjust the algorithm to say that the focused element is a priority, but we don't directly select priority elements but rather use the priority element as the root for the rest of the scroll anchoring: we start at the focused element, if one exists, and then run the typical scroll anchoring that selects the first element that is fully visible in that subtree

This makes sense for the non-text focus cases, but for text cases like contenteditable I think you really do want to find the nearest element at or before the cursor. This is the same logic behind why we use the focused element even if it wouldn't normally be the element selected for anchoring because it's not near the top of the page. I can make an example page to demonstrate this.

@tommoor
Copy link

tommoor commented Feb 20, 2025

In case it's useful I'd like to link the original Chromium issue which spawned this discussion – it includes a demo of how the current behavior is less than ideal: https://issues.chromium.org/issues/395489580

@vmpstr vmpstr added the Agenda+ label Feb 20, 2025
@vmpstr
Copy link
Member Author

vmpstr commented Feb 20, 2025

This makes sense for the non-text focus cases, but for text cases like contenteditable I think you really do want to find the nearest element at or before the cursor. This is the same logic behind why we use the focused element even if it wouldn't normally be the element selected for anchoring because it's not near the top of the page. I can make an example page to demonstrate this.

The reason I think the approach of starting with the focused root would work, is that this is the same behavior you get when contenteditable isn't focused (which has the correct behavior). Specifically in that case, we simply treat that element as any other and select a scroll anchor as the deepest fully visible child of the element. The downside would be that if content changes within the visible portion of contenteditable then it would indeed push the cursor down.

So yeah, maybe finding the cursor element is better, it's just a bit more of a special case

@vmpstr
Copy link
Member Author

vmpstr commented Feb 20, 2025

Proposal for the agenda:

When contenteditable element has focus:

  • Option 1: Treat that focused element as a root of the algorithm where we descend into it to find the best scroll anchor (first fully visible element, iirc)
    • Pros: fixes the problem
    • Cons: elements inserted into the visible portion of the contenteditable push the cursor down
  • Option 2: Find the element that is nearest preceeding to the cursor and use that as the scroll anchor
    • Pros: best solution keeping the cursor as stable as possible
    • Cons: a bit more of a special case for contenteditable

@flackr
Copy link
Contributor

flackr commented Feb 20, 2025

The reason I think the approach of starting with the focused root would work, is that this is the same behavior you get when contenteditable isn't focused (which has the correct behavior).

Only if the contenteditable is at or above the top of the scrollport.

That's why I think we should do both,

  1. If you have a focused contenteditable, use the nearest preceding element to the cursor.
  2. Otherwise, if you have a focused element, use it as the root.

As an example of case 2, I might have a focused tab panel or tree view which contains a large amount of content but is not editable. I would want the part of the focused item I've scrolled to to remain in view if possible which we would do by using it as a root rather than just using it as the anchor.

@vmpstr
Copy link
Member Author

vmpstr commented Feb 21, 2025

flackr and I talked a bit offline. One idea that Rob had is to only use a deep node anchor for its immediate scroll parent. Then using that scroll parent as the anchor for the next scroll parent and so on (Rob correct me if I'm wrong). All of this started because if we use the cursor there's a weird interaction with overflow: clip contenteditable thing that ends up scrolling the document to keep the cursor in place, which feels wrong.

I think the solution of scroller anchoring chaining may work but also carries quite a bit of risk, since we haven't yet thought through all of the edge cases involved.

Personally, I still prefer the solution of using the priority candidate as the root for the node selection algorithm, instead of just using the priority candidate directly. That seems like a small change and aligns with the scroll anchor algorithm if there was no priority candidate (ie if you remove the focus from the contenteditable field then the behavior you get is the same). So I like that option from the consistency perspective as well.

I'm happy to limit this to just contenteditable things or any priority candidate

@flackr
Copy link
Contributor

flackr commented Mar 11, 2025

I still feel that it would be better for the user to anchor at the location of the cursor. I feel like this is analogous to the idea of using the focused element as the anchor root. Wouldn't we have the same "degenerate" behavior that you are worried about if the focused element's location moves within an overflow: clip?

@vmpstr
Copy link
Member Author

vmpstr commented Mar 12, 2025

Yes, I think that would be a similar behavior, albeit more intentionally constructed than just contenteditable.

In my testing I found the cursor anchoring case to be unexpected and it looked strange to me (within contenteditable). Added to this, the scroll anchoring implementation for cursor anchoring also has to track selection and user cursor, which is added implementation complexity. The node selection from candidate change that I propose appears to me to be a smaller incremental change that is less likely to cause "good cases" to break, which is why I'm still leaning towards that

@astearns astearns changed the title [css-scroll-anchoring] anchoring within conteneditable elements [css-scroll-anchoring] anchoring within contenteditable elements Mar 18, 2025
@astearns astearns moved this to Regular agenda in CSSWG April 2025 meeting agenda Mar 27, 2025
@w3c w3c deleted a comment from css-meeting-bot May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Regular agenda
Development

No branches or pull requests

3 participants