I created an svg text
and I need to create a responsive rect
angle outside the text
using a svg
(no div
).
Since the text
lenght may change based on window size, also the rect
width should change.
Here a working example and below my code:
App.svelte
:
<script>
import Wrapper from './Wrapper.svelte'
let w = 0
$: h = Math.min(w, 300)
function getTileLabel(w) {
if (w < 200) {
return (d) => d.name
}
return (d) => `Long label ${d.name}`
}
const data = [{name: 'a', y: 0}, {name: 'bbbbb', y: 50}, {name: 'ccc', y: 100}]
$: getLabel = getTileLabel(w)
</script>
<div bind:clientWidth={w}>
<Wrapper
w={w}
h={h}
{data}
{getLabel}
/>
</div>
Wrapper.svelte
:
<script>
import Container from './Container.svelte'
export let w
export let h
export let data
export let getLabel
</script>
<svg x={0} y={0} width={w} height={h}>
{#each data as d}
<Container {d} label={getLabel(d)} />
{/each}
</svg>
<style>
svg {
border: 2px solid black;
}
</style>
Container.svelte
:
<script>
export let d
export let label
let textElement
$: textElementBB = textElement && textElement.getBoundingClientRect()
$: x0 = 0
$: y0 = d.y
$: width = d.width
$: height = d.height
$: borderWidth = textElementBB?.width
$: borderHeight = textElementBB?.height
$: borderX = x0
$: borderY = y0
$: textX = borderX
$: textY = borderY
</script>
<rect x={borderX} y={borderY} width={borderWidth} height={borderHeight} stroke="tomato" stroke-width={2} fill="none" />
<text
bind:this={textElement}
fill="black"
x={textX}
y={textY}
alignment-baseline="hanging"
>
{label}
</text>
The text
becomes smaller if you resize the window but the rect
width does not change. Why?
I think the problem is because the reference of the text
node does not change when label
change.
So I think the problem is here:
let textElement
am I right? How can I solve? Is there something like an useEffect
of React for Svelte?
CodePudding user response:
Reactivity is based on assignments and events (when using bindings). Here you are only referencing the element which is not reassigned.
There are two options I can think of, both of which drop the use of getBoundingClientRect
:
- Directly use bindings for
clientWidth
andclientHeight
on the element - Add a
ResizeObserver
that observes the element and update the values in its callback
CodePudding user response:
A useEffect-ish function would look something like
<script>
let borderWidth = 0
let borderHeight = 0
let textElement
$: {
// EVERY line within the object will execute when ANY variable within the object changes. In this case, textElement will change and this object will execute.
borderWidth = textElement && textElement.getBoundingClientRect().width
borderHeight = textElement && textElement.getBoundingClientRect().height
}
</script>
The React version of this would be
let borderWidth = 0
let borderHeight = 0
let textElement
useEffect(() => {
borderWidth = textElement && textElement.getBoundingClientRect().width
borderHeight = textElement && textElement.getBoundingClientRect().height
}, [borderWidth, borderHeight, textElement]
One step further, let's say you only wanted to react when textElement changed and NOT when borderWidth or borderHeight change. For that you would declare a function outside of the object and call it from with the object.
<script>
let borderWidth = 0
let borderHeight = 0
let textElement
const updateBorderDims = () => {
borderWidth = textElement && textElement.getBoundingClientRect().width
borderHeight = textElement && textElement.getBoundingClientRect().height
}
$: {
// do something to get the dependancy within the object, here I'm declaring another variable t - this variable isn't passed
let t = [textElement]
updateBorderDims()
}
</script>