I have a situation where I need to scroll a list item into view that is initially hidden. The issue is that if I don’t use any delays the before calling scrollIntoView() on the list item to focus, the element is almost always null because it is not yet rendered.
A demo illustrating my issue: https://svelte.dev/repl/49c5b57097574017929649e08c001754?version=3.49.0
I have found some workarounds:
-
The first one I tried was to wrap the scroll function in an arbitrary delay and hope the list is rendered after the delay, but it’s not optimal because of different render speeds on different computers and not a very clean solution either (this is illustrated in the first demo above).
-
The second one is better. I wrapped the list in an element and added a transition and triggered the scroll on the
introendevent. This always seem to work, but it feels like a hack (albeit not a big hack). Demo: https://svelte.dev/repl/c2583b73e376479a90fa185f2f3baa6e?version=3.49.0
Is workaround #2 the de facto way to do it, or is there another way to manage these types of situations where your JS is dependent on the dom state?
>Solution :
For DOM interactions like this, actions are a good fit.
Here you would add the action to the element containing the list, it will be triggered once the list is fully rendered. If the action were to be added on an item, the action would trigger as soon as the item that should be focused is created and the centering would not work, because at that point there are no items after it.
E.g.
function focus(node) {
node.querySelector('.focused-item')
.scrollIntoView({ block: 'center' });
}
<section use:focus>
{#each listItems as item}
...
{/each}
</section>
You can make this reactive by e.g. defining a variable that identifies the item and passing that to the action.
let errorItem = 'item2';
// ...
function focus(node) {
const update = () => {
const item = node.querySelector('.focused-item');
if (item)
item.scrollIntoView({ block: 'center' });
}
update();
return { update };
}
<section use:focus={errorItem}>
{#each listItems as item}
<div class:focused-item={item === errorItem}>
{item}
</div>
{/each}
</section>
The parameter does not necessary have to be used in the action. In this case it is just used to trigger the update function returned from the action.