[csswg-drafts] Event listener for client rectangle changes (#3981)

Azmisov has just created a new issue for https://github.com/w3c/csswg-drafts:

== Event listener for client rectangle changes ==
I need a way to listen to changes in the client rectangles of an element. Some use cases I have had for this but was never able to implement:
- A typography tool for a wysiwyg that overlays dimension helpers like baseline, line-height, etc: It would need to know the dimensions of the individual client boxes, and be able to react to changes like line-wrapping, reflow, etc.
- Multi-editing selection for wysiwyg documents: Since only one selection range is allowed in most browsers, you need some way to emulate selection yourself. Code editors (Codemirror/VSCode) can implement this feature fairly easily with CSS, since code editing is a very constrained case. But for more general wysiwyg's, you really need a way to overlay a semi-transparent element, that reacts to the changes in the client rectangles.
- "Pinning" widgets to a corner or edge of an element: Here, you'd have a widget isolated in a separate part of the DOM tree, and you want to pin its absolute position on the page to some other element.

Specific reasons why these can't be implemented using other methods are listed below.

The developer tools for browsers allow you to highlight an element. And surprisingly (to me), this is able to highlight inline elements correctly, even when they split or wrap to multiple lines. The rulers/highlighter elements also dynamically reposition themselves if the target element is resizing. I looked into the source code for Gecko, and the developer tools implements a sort of client rectangle event listener already (see reflow tracker, reflow front, getAdjustedQuads in the source code). Couldn't we expose this kind of interface for use in general web apps? I'm guessing the code in use for browsers' developer tools could be reused.

### Partial solutions that don't really work:
_Shadow DOM:_ The approach here is to insert your helper element (typography rulers, selection overlay, pinned widget) inside the target element. Then, the helper could react to changes in the target's dimensions or position using CSS. The first problem with this is that some inline elements do not allow a shadow dom to be attached. For example, image can't have a shadow dom, yet it still has a client rectangle that we would want to react to. We could get around this by wrapping image in our own custom element; although intrusive for a wysiwyg use-case, its doable.

The second problem is it wouldn't work for inline elements. To correctly position the helper element, the container needs relative positioning at least; relative positioning is not possible with inline. Another complication is that inline elements can be split into multiple lines, so you really need a helper element per-client box.

_ResizeObserver:_ Again, the problem is with inline elements. The overall content/border box may not be modified, even though the size of the enclosed content may have changed. See this example to see what I mean: https://jsfiddle.net/azmisov/p4j5q7z6/1/. You'd want the child `strong` element to notify when it wraps to the next line; and you'd want the container `div` to notify when more text is added and the text lines change size. Currently, there isn't a way to detect when these client rectangles have changed and react to them.

The second problem is that ResizeObserver is only built to handle changes in dimensions. Even if you added a "clientrect-box" option to the ResizeObserver interface, you'd still need some way to know when the client rect has moved, and reposition the helper elements accordingly. For example, text is added in a wysiwyg and an inline image shifts to the right; it has not resized, but its client rectangle has changed and you would want to react to that.

_Others:_ Using a CSS filter (sepia+hue rotate) gets close to faking a selection overlay effect. This doesn't work for semi-transparent / display:none inline elements, since the CSS filter will have no effect on an invisible element. Nor will it work for partially selected TextNodes, since the CSS filter applies to entire element, rather than just the selected portion of the text. So the only option I believe is to use an absolute positioned overlay element. The overlay element needs to react to the target's client rectangles to stay in the correct position (and size).

In some cases, you can use relative positioning on a container, and then make your pinned widget absolute positioned. As I mentioned before, this won't work for inline elements. But also, a more general problem is you often times want these helper widgets segregated. A CKEditor developer has said that they have had to do some very complicated workarounds implementing widgets into their wysiwyg. Having the widgets in a separate part of the DOM tree, segregated from the contenteditable section, would greatly simplify these kinds of features. I suspect it could be utilized by the Google docs / MS word teams as well.

Sometimes you can pin an element using css sticky positioning, or recalculate its position/size when the window is scrolled or resized. Unfortunately, there are many cases where the position/size of a target element are not easily predictable (e.g. inline flow elements, animated elements, etc). Especially if you are trying to write a wysiwyg or other custom input editor like me, you really want a solution for the general case... reacting to client rectangle changes.

---------

_(Not sure what part of the CSS spec this falls under... if someone can add the tags for me)_

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/3981 using your GitHub account

Received on Monday, 27 May 2019 17:24:51 UTC