sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.76k stars 4.14k forks source link

Add `bind:boundingClientRect` #13412

Open nmzein opened 2 weeks ago

nmzein commented 2 weeks ago

Describe the problem

I was working on creating user movable applets and came across many scenarios were I needed to reactively have the boundingClientRect of a div. This is not too hard to implement manually but would be really nice to have built into Svelte to avoid the boilerplate.

Describe the proposed solution

Add the ability to bind to the boundingClientRect of an html element using bind:boundingClientRect .

Importance

nice to have

Prinzhorn commented 2 weeks ago

Duplicate of https://github.com/sveltejs/svelte/issues/8630

https://svelte.dev/repl/9f673deeaba84466ad9b216a5629191b?version=4.2.19

nmzein commented 2 weeks ago

That issue doesnt seem to mention boundingClientRect. I know that svelte already has bindings for contentRect among other things but these do not provide the same functionality. Unless I'm mistaken

Prinzhorn commented 2 weeks ago

Can you explain what exactly the difference is for your use-case?

nmzein commented 2 weeks ago

Im not aware of any of the existing bindings giving absolute x, y positions of the bounded to div. ContentRect just gives the dimensions of the div without taking into account its position at all. Binding to boundingClientRect would also allow you to get the clientWidth and clientHeight at the same time as the absolute x and y pos without making extra bindings for those separately. This is another area where contentRect differs from boundingClientRect where contentRect doesnt include the padding in the width and height of the object which forces you to either calculate it or use clientWidth and clientHeight bindings.

dummdidumm commented 2 weeks ago

This sounds like something you can solve through an action.

nmzein commented 2 weeks ago

Which is what I did eventually. Thought it would be nicer to have this built-in though since it can be confusing as to why it doesn't exist. If theres no drawbacks to having this included is there a reason not to?

Prinzhorn commented 2 weeks ago

If theres no drawbacks

Don't you need to poll this? How did you implement it? Maybe having this as a binding is a footgun when you should only call getBoundingClientRect when you need to.

brunnerh commented 2 weeks ago

Don't you need to poll this?

Not necessarily, the existing bindings use a ResizeObserver if possible.

Prinzhorn commented 2 weeks ago

Not necessarily, the existing bindings use a ResizeObserver if possible.

getBoundingClientRect depends on the scroll position (among others), so you'd need to poll it or imperatively call it when you need it (I wouldn't poll it with raf). You can't solely rely on ResizeObserver. That's what I meant with "having this as a binding is a footgun". It needs to be used carefully depending on the requirements.

nmzein commented 1 week ago

Yes you do have to poll because ResizeObserver would only run when the div was resized not when it was dragged and moved. I personally think even if its a "footgun" it should be available since if people really need it they will create it themselves anyways. Polling does not create that bad of a performance penalty unless used in excess. IMO this is where documentation steps in to tell people to use things carefully.

brunnerh commented 1 week ago

The size bindings also used polling as a fallback (but will switch to ResizeObserver permanently in v5). The polling was only initiated if a binding existed, so it's not degrading general performance.

nmzein commented 1 week ago

Yeah but boundingClientRect doesnt only give you size information but positional. I dont believe a ResizeObserver would be able to detect changes to position no? Please correct me if I'm wrong.

brunnerh commented 1 week ago

Yes, I was just pointing out the precedent for polling and its implications. As @Prinzhorn already noted, an observer would not be enough here, unfortunately.

nmzein commented 1 week ago

Right. So what I'm getting is you wouldnt be against adding this even though its implemented using polling right? If thats the case I could maybe try to navigate myself through the source code and start figuring out what needs to be added.

brunnerh commented 1 week ago

I'm not a maintainer, @dummdidumm is, and he suggested to use an action. With Svelte 5 coming up, I'm inclined to agree.

In earlier versions, the ergonomics of having an action that updates a value were not great. Usually you would have to do things like using a separate store or maybe pass a setter function around.

With runes you can create a utility that hosts an action and the value. So the end user code really does not change all that much, e.g.

<script>
+   import { createClientRectTracker } from './client-rect-tracker.svelte.js';

-   let clientRect = $state(null);
+   const clientRectTracker = createClientRectTracker();
</script>

- X: {clientRect?.x} <br>
- Y: {clientRect?.y} <br>
+ X: {clientRectTracker.value?.x} <br>
+ Y: {clientRectTracker.value?.y} <br>

- <div bind:boundingClientRect={clientRect}> ... </div>
+ <div use:clientRectTracker.action> ... </div>

Example action (Try this in Firefox. Chromium throttles the polling due to some broken out-of-focus detection or something like that.)

With a separate action you also have more control, you can e.g. provide an option to set the polling interval.