Reputation: 8345
I'm implementing an app where the user should be able to place things on a 2D grid by clicking on it, and also be able to drag the grid around. I have a functional prototype here:
https://svelte.dev/repl/ebfde34fafda4b79859f70dd8f984b12?version=3.54.0
<script>
let project = {
offsetX: 0,
offsetY: 0,
circles: [{
id: 1,
x: 50,
y: 50,
}]
}
let dragInfo = null
function onBackgroundDragStart(event){
dragInfo = {
action: "moveBackground",
dragStartProjectOffsetX: project.offsetX,
dragStartProjectOffsetY: project.offsetY,
startMouseX: event.clientX,
startMouseY: event.clientY
}
const dragImage = document.createElement("img")
dragImage.style.display = "none"
event.dataTransfer.setDragImage(dragImage, 0, 0)
}
function onBackgroundDragOver(event){
switch(dragInfo.action){
case "moveBackground":
const dragDistanceX = event.clientX - dragInfo.startMouseX
const dragDistanceY = event.clientY - dragInfo.startMouseY
project.offsetX = dragInfo.dragStartProjectOffsetX + dragDistanceX
project.offsetY = dragInfo.dragStartProjectOffsetY + dragDistanceY
break
}
}
// Create new circle on click.
function onClick(event){
project.circles.push({
id: project.circles.length + 1,
x: event.clientX - project.offsetX,
y: event.clientY - project.offsetY,
})
project.circles = project.circles
}
function getText(circle){
console.log("getText() is called.")
return circle.x.toString()[0]
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="app"
draggable="true"
on:dragstart={onBackgroundDragStart}
on:dragover|preventDefault={onBackgroundDragOver}
on:click={onClick}
style:background-position-x={`${project.offsetX}px`}
style:background-position-y={`${project.offsetY}px`}
>
{#each project.circles as circle (circle.id)}
<div
class="circle"
style:transform={`translate(${
project.offsetX + circle.x
}px, ${
project.offsetY + circle.y
}px) translate(-50%, -50%)`}
>
{getText(circle)}
</div>
{/each}
</div>
<style>
.app{
position: relative;
width: 100vw;
height: 100vw;
background-color: silver;
position: relative;
background-image: linear-gradient(rgba(0, 0, 0, .1) .1em, transparent .1em), linear-gradient(90deg, rgba(0, 0, 0, .1) .1em, transparent .1em);
background-size: 2em 2em;
overflow: hidden;
}
.circle{
position: absolute;
width: 50px;
height: 50px;
background-color: red;
border-radius: 50%;
text-align: center;
}
</style>
The prototype works as expected, but Svelte recomputes too much on changes:
project.offsetX
and project.offsetY
are updated, but in {#each project.circles ...}
, getText(circle)
is called again for each circle, even though it just makes use of the circle.x
and will never change when project.offsetX
and project.offsetY
getText(circle)
is called for each circle again)This is very problematic in my case, because getText()
is in my true case another function that is very expensive to call, so it's important it's not being called unnecessary many times (it starts to lag).
I know I can get around (1) in the list above by putting the circles
array in it's own variable, but that is not really a good solution, since I need to store all information about the project in one and the same object (so I later conveniently can save it in a JSON file), so I would prefer to not use that. And the problem with (2) still exists.
How can I get Svelte to not update unnecessarily much?
Upvotes: 1
Views: 402
Reputation: 9979
I do not think the problem lies with how Svelte re-renders/updates. When you modify a component, for instance in your case when you drag the grid, it is normal for Svelte to re-render the circles in order to properly repaint them in your browser. The same applies when you update your list of circles (although I am a bit surprised all circles are re-rendered when you simply add a circle considering you properly add a (circle.id)
key to your #each
loop?).
The solution to your expensive computation issue is to use memoization in order to cache expensive computation results, thus making subsequent function calls with identical inputs virtually free of computation cost. The exact implementation is really up to you, though you will want your expensive computation function to be pure (i.e. no side-effects, and you always get the same output from given inputs) for memoization to function properly.
Once you have memoization in place, expensive computation should not be an issue anymore, and neither will be Svelte's re-rendering.
Upvotes: 1