varabyte / kobweb

A modern framework for full stack web apps in Kotlin, built upon Compose HTML
https://kobweb.varabyte.com
Apache License 2.0
1.51k stars 66 forks source link

Issue With DOM Ref #308

Closed alexwhb closed 1 year ago

alexwhb commented 1 year ago

When I use ref on a Box and try accessing element.getBoundingClientRect() it gives me the proper width and height to the element, but incorrect x, y, left, right... etc values. I tested the same thing with a Div to make sure it wasn't an issue with the jetbrains compose side, and that worked as expected, so I'm thinking maybe it has something to do with the caching system or something on the kobweb/silk side?


Here's a sample of the code I'm working with:

Issue code:

 Box(ref = ref { element ->
                            console.log(element.getBoundingClientRect())
                }) 

code in context:

Row {
                Image(src = "/img/elpi-square-400.jpg", modifier = Modifier.height(120.px))

                Column(
                    Modifier.margin(top = 12.px),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Row(
                        Modifier
                            .width(250.px)
                            .height(75.px),
                        horizontalArrangement = Arrangement.SpaceBetween,
                        verticalAlignment = Alignment.CenterVertically
                    ) {

                        Image(
                            "/icons/play-controls/skip-back-30.svg",
                            modifier = Modifier.onClick { })
                        Image(
                            "/icons/play-controls/skip-previous.svg",
                            modifier = Modifier.onClick { })
                        Image(
                            "/icons/play-controls/play-button.svg",
                            modifier = Modifier.onClick { })
                        Image("/icons/play-controls/skip-next.svg", modifier = Modifier.onClick { })
                        Image(
                            "/icons/play-controls/skip-forward-30.svg",
                            modifier = Modifier.onClick { })
                    }

// this works as expected
                    Div(attrs = {
                        ref {
                            console.log(it.getBoundingClientRect())
                            this.onDispose {}
                        }
                    }) {  }

                    // the play seek bar
                    Box(Modifier
                        .onMouseEnter { isHovering = true }
                        .onMouseLeave { isHovering = false }
                        .onMouseMove { event ->
                            if (!seekPressed) return@onMouseMove
                            val offsetX = event.clientX - (playBarRect?.left ?: 0.0)
                            val sliderWidth = playBarRect?.width ?: 600.0
                            val newValue = (offsetX / sliderWidth) * sliderWidth
//                                        val newValue = event.clientX.toDouble()

                            console.log(offsetX, sliderWidth, newValue, playBarRect)
                            // Ensure newValue stays within the slider's range
                            val minValue = 0.0
                            val maxValue = sliderWidth
                            val clampedValue = min(maxValue, max(minValue, newValue));
//                                        console.log(clampedValue)
                            seekPos = clampedValue
                        },
                        ref = ref { element ->
                            // Triggered when this Box is first added into the DOM
//                            playBarRect = element.getBoundingClientRect()

                            console.log(element.getBoundingClientRect())
                        }

                    ) {
                        // background
                        Box(
                            Modifier.width(600.px)
                                .height(4.px)
                                .backgroundColor(Theme.Color.mid)
                                .borderRadius(3.px),
                        )

                        // foreground blue
                        Box(
                            Modifier.width(300.px)
                                .height(4.px)
                                .backgroundColor(if (isHovering) Theme.Color.primary else Theme.Color.textPrimary)
                                .borderRadius(3.px)
                        )

                            Box(
                                Modifier
                                    .width(14.px)
                                    .height(14.px)
                                    .translate(tx = seekPos.px, ty = (-5).px)
                                    .borderRadius(50.percent)
                                    .backgroundColor(Theme.Color.textPrimary)
                                    .onMouseDown {
                                        seekPressed = true
                                    }
                                    .onMouseUp {
                                        seekPressed = false
                                    }
                            )
                    }

                }
            }
alexwhb commented 1 year ago

After more investigation... It might actually be an issue on the Jetbrains side... if I do

Box(ref = ref {console.log(it.getBoundingClientRect()) })

That will output the correct X, left, right values... so it seems that in order to get correct x, left, right values... you need your HTML element to not contain any children strangely.

alexwhb commented 1 year ago

I figured it out... you have to use ref later... probably ref happens before the DOM is actually rendered, which makes the results unpredictable if you just getBoundingClientRect() inside ref {} 🙌, so instead I store that ref and call getBoundingClientRect() later.

bitspittle commented 1 year ago

I love coming in just after the person already figured it out :)

Yeah I was going to suggest postponing the ref calculation. You can do this as you did, by storing the ref, or within the ref, you can try calling window.invokeLater { ... } (an extension method provided by Kobweb) or window.setTimeout(0) { ... } if you prefer.

alexwhb commented 1 year ago

@bitspittle awesome! Thank for for these additional tips! This is great!