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:
- When dragging the background, only
project.offsetX
andproject.offsetY
are updated, but in{#each project.circles ...}
,getText(circle)
is called again for each circle, even though it just makes use of thecircle.x
and will never change whenproject.offsetX
andproject.offsetY
- Same when adding a circle (
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?