'Trying to resize a component onUpdate
I've written a component that renders strings from an array as spans in a div if there is space sufficient width available in the div.
Calling the renderDinos() method onMount() works as expected. From there I wanted to update the component to dynamically re-render the strings in the div (adding or subtracting strings from the array) when the width of the div changes.
To achieve this I'm calling renderDinos() again in the afterUpdate() if the width of the component has changed. When onMount() runs the scrollWidth is the width of dinosaurs[i] so the scrollWidthSum is calculated accurately.
When the same method is called by afterUpdate() the initial value of scrollWidth is 0, not the width of dinosaurs[i]. This messes up the calculation for scrollWidthSum. I'd also like to subtract the scrollWidth from currentWidth in the if block in order to align the children better but, again; the value is initially 0 when I call the method from onUpdate.
Is there a way to resolve this? Is there a simpler way to re-render the content of the div when it resizes?
<script>
import { onMount, tick, afterUpdate } from 'svelte'
let dinosaurs = [
'dinosaur1',
'dinosaur2',
'dinosaur3',
'dinosaur4',
'dinosaur5',
]
let currentWidth = 0,
previousWidth = 0,
scrollWidth,
scrollWidthSum
$: dinosToShow = []
onMount(async () => {
previousWidth = currentWidth
renderDinos()
})
afterUpdate(() => {
if (previousWidth != currentWidth) {
renderDinos()
previousWidth = currentWidth
}
})
async function renderDinos() {
dinosToShow = []
scrollWidthSum = 0
scrollWidth = 0
for (let i = 0; i < dinosaurs.length; i++) {
dinosToShow = [...dinosToShow, `${dinosaurs[i]},\xa0`]
await tick()
console.log(scrollWidth)
scrollWidthSum += scrollWidth
if (scrollWidthSum > currentWidth - 60) {
dinosToShow.pop()
await tick()
break
}
}
}
</script>
<style>
.display {
overflow: hidden;
}
.dinosaur {
display: inline-block;
}
</style>
<div class="display" bind:clientWidth={currentWidth}>
{#each dinosToShow as r}
<span bind:clientWidth={scrollWidth} class="dinosaur">{r}</span>
{/each}
</div>
Solution 1:[1]
You are binding the clientWidth of each span to the same variable, changing it as you progress through the each loop, this is considered a bad practice.
I think it would be better to do something like this (added comments for some explanation) :
<script>
let dinosaurs = [
'dinosaur1',
'dinosaur2',
'dinosaur3',
'dinosaur4',
'dinosaur5',
]
let currentWidth = 0;
// have the array updated when currentWidth updates
$: dinosToShow = render(currentWidth);
function render(currentWidth) {
// Early return
if (currentWidth == 0) return [];
let i = 0;
let sumWidth = 0;
// While instead of for, removes the need for breaks
while(i < dinosaurs.length && sumWidth < currentWidth) {
// Create a dummy span and add it somewhere far off screen
let span = document.createElement('span')
span.classList.add('dinosaur')
span.textContent = `${dinosaurs[i]},\xa0`
span.style = "visibility: hidden; position: fixed; top: -1000;";
document.body.appendChild(span);
// Sum the width of the dummy elements
sumWidth += span.getBoundingClientRect().width
// Remove the dummy again
document.body.removeChild(span)
// if the sum with the dummy is less than the current width do i + 1
// this will effectively include this dino in the list
if (sumWidth < currentWidth) {
i++
}
}
// return the subarray that fits.
// note that if sumWidth > currentWidth this will NOT include the dino that was too big
return dinosaurs.slice(0, i)
}
</script>
<div class="display" bind:clientWidth={currentWidth}>
{#each dinosToShow as r}
<!-- No binding of stuff here -->
<span class="dinosaur">{r}</span>
{/each}
</div>
<style>
.display {
overflow: hidden;
}
.dinosaur {
display: inline-block;
}
</style>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Stephane Vanraes |
