I've a basic layout built in Svelte. It's composed by two columns: a darkpurple on the left and a lightgrey on the right. The left column has fixed width and the user can open and close it using the red button, the right one takes the remaining space. To this that, I used:
bind:clientWidth={width}
bind:clientHeight={height}
I need the dimensions because inside the right column I have to add a new component (Core
) that do some expensive computations with that values.
The problem is that when the user changes the window size, width and height are obviously recomputed and so also the computation of the Core
component and it's very slow.
Is there a way to debounce width and height computation?
Here my code:
App.svelte
<script>
import LeftColumn from "./LeftColumn.svelte";
import RightContent from "./RightContent.svelte";
let width = 0;
let height = 0;
let isLeftColumnOpen = true;
</script>
<main >
<div class="container">
<LeftColumn {isLeftColumnOpen}/>
<div
class="inner-container"
bind:clientWidth={width}
bind:clientHeight={height}
>
<!-- this div is necessary only to prevent the RightContent component from continuing to render increasing in size -->
<div class="content-container">
<RightContent {width} {height} />
</div>
</div>
<div class="left-column-button" on:click={() => isLeftColumnOpen = !isLeftColumnOpen}>{isLeftColumnOpen ? 'close' : 'open'}</div>
</div>
</main>
<style global>
:global(body, html) {
margin: 0px;
padding: 0px;
}
main {
font-family: sans-serif;
position: fixed;
overflow: hidden;
}
.container {
display: flex;
width: 100vw;
height: 100vh;
position: relative;
}
.inner-container {
background: lightgrey;
display: flex;
flex-grow: 1;
height: 100%;
position: relative;
}
.content-container {
position: absolute;
}
.left-column-button {
position: absolute;
bottom: 10px;
right: 10px;
background-color: tomato;
cursor: pointer;
}
</style>
LeftColumn.svelte:
<script>
export let isLeftColumnOpen;
</script>
<div class="container" style="width: {isLeftColumnOpen ? '300px': '0px'}">
</div>
<style>
.container {
height: 100%;
background: darkslateblue;
color: white;
transition: all;
animation-duration: 500ms;
}
</style>
RightContent.svelte:
<script>
export let width;
export let height;
</script>
<div style="width: {width}; height: {height}; border: 1px solid red;">{`${width} x ${height}`}</div>
As a basic mechanism you can avoid using the dimensions directly and update the state via timeouts.
E.g.
let width;
let height;
// This contains the debounced values
let dimensions = { height, width };
let timeoutHandle;
$: {
clearTimeout(timeoutHandle);
timeoutHandle = setTimeout(() => {
dimensions = { height, width };
}, 200);
}
You can also extract the debounce logic into an action, though you then have to use stores to preserve reactive output; i.e. you pass in a store and the action updates it.
The action should use a ResizeObserver
for the best performance.
E.g.
import { writable } from 'svelte/store';
export function observeDimensions(node, options) {
const { timeout, store } = {
timeout: 200, // Default for timeout option
...options,
};
let handle;
const observer = new ResizeObserver(() => {
clearTimeout(handle);
handle = setTimeout(() => {
store.set({ width: node.clientWidth, height: node.clientHeight });
}, timeout);
});
observer.observe(node);
return {
destroy() {
clearTimeout(handle);
observer.disconnect();
}
}
}
export function dimensionsStore() {
return writable({ width: -1, height: -1 });
}
Usage:
<script>
import { observeDimensions, dimensionsStore } from './dimensions';
const dimensions = dimensionsStore();
</script>
<div use:observeDimensions={{ store: dimensions }}>
Content
</div>
Debounced: {$dimensions.width}x{$dimensions.height}