Skip to content
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-scroll-anchoring] anchoring within conteneditable elements #11748

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

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

vmpstr opened this issue Feb 19, 2025 · 8 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants