Search code examples
sveltesveltekitsvelte-transition

Combining transitions with reactive content


I'm working on a chat application in SvelteKit. When the user sends a message, the user message should slide into the chatlog, and then an ellipsis should slide into the chatlog. When the chat response is ready, the ellipsis should be replaced by the response message, and it should show up using a typewriter transition.

I've solved this by having a messages array, and a messageCount variable. When the user sends a message, both the message and the ellipsis is appended to messages, and messageCount is incremented. The {#each} block showing the chatlog slices messages so that only the first messageCount messages are shown. When the user message has been transitioned in, messageCount is incremented again using on:introend, so that the ellipsis slides in.

The message content is in a <span> with a typewriter transition. It is wrapped in a {#key} block to ensure that the transition is run when the ellipsis is replaced by the actual chat response message.

This works well as long as the ellipsis comes into the chatlog before it is replaced by the actual chat response message. If the slide transition takes longer than the delay before the ellipsis is replaced, the typewriter transition does not take place. Why is that?

Here is the most important logic:

function sendMessage() {
    messages = [
        ...messages,
        { "role": "user", "content": message },
        { "role": "assistant", "content": "…" },
    ];
    setTimeout(() => {
        messages = [
            ...messages.slice(0, messages.length - 1),
            { "role": "assistant", "content": "Response to chat message" },
        ];
    }, 1000); // Set delay to 100, and the typewriter transition will stop working.
    ++messageCount;
}
{#each messages.slice(0, messageCount) as message}
    <ul>
        <li in:slide on:introend={showNextMessage}>
            <!-- Remove the {#key} block and the typewriter transition stops working for delay 1000, but starts working for delay 100 -->
            {#key message.content}
                <span in:typewriter={message.role === "user" ? Infinity : 25}>
                    {message.content}
                </span>
            {/key}
        </li>
    </ul>
{/each}

Here is a working REPL: https://svelte.dev/repl/9331addfc5b940dd85ff6cbdc0565c76?version=4.2.1


Solution

  • The solution was a simple as adding the global modifier to the transition:

    {#each messages.slice(0, messageCount) as message}
        <ul>
            <li in:slide on:introend={showNextMessage}>
                <!-- Remove the {#key} block and the typewriter transition stops working for delay 1000, but starts working for delay 100 -->
                {#key message.content}
                    <span in:typewriter|global={message.role === "user" ? Infinity : 25}>
                        {message.content}
                    </span>
                {/key}
            </li>
        </ul>
    {/each}