Categories
Svelte

Advanced Svelte Transition Features

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to use advance Svelte transition features, including transition events, and local and deferred transitions.

Transition Events

We can add event handlers for various transition events to do things like logging.

For instance, we can write the following code to add an animation for our p element when it’s being toggled on and off and show the status of the animation:

App.svelte :

<script>
 import { fly } from "svelte/transition";
 let visible = true;
 let status = "";
</script>

<button on:click={() => visible = !visible}>Toggle</button>
<p>Animation status: {status}</p>
{#if visible}
<p
  transition:fly="{{ y: 200, duration: 2000 }}"
  on:introstart="{() => status = 'intro started'}"
  on:outrostart="{() => status = 'outro started'}"
  on:introend="{() => status = 'intro ended'}"
  on:outroend="{() => status = 'outro ended'}"
>
  foo
</p>
{/if}

In the code above, we add event handlers for introstart , outrostart , introend , and outroend events which set the status .

Then we display the status in a p tag. As we click the Toggle button, we’ll see the animation happen and the status being updated.

Local Transitions

We can apply transitions to individual list elements with local transitions. For instance, we can write the following to add a slide effect to each item on a list:

App.svelte :

<script>
  import { slide } from "svelte/transition";
  let items = [];
  const addItem = () => {
    items = [...items, Math.random()];
  };
</script>

<button on:click={addItem}>Add Item</button>
{#each items as item}
  <div transition:slide>
    {item}
  </div>
{/each}

In the code, we have the addItem function to add a new number to an array. Then we display the array in the markup.

In the div inside the each , we add the transition:slide directive to apply a slide effect when the item is added.

Therefore, we’ll see the slide effect as we click Add Item.

Deferred Transitions

With Svelte, we can defer transitions so that we can coordinate animation between multiple elements.

We can use the crossfade function to achieve this. It creates a pair of transitions called send and receive .

If the element is sent, then it looks for a corresponding element being received and generates a transition that transforms the element to its counterpart’s position and fades it out.

When an element is received, then the opposite happens. If there’s no counterpart, then the fallback transition is used.

For instance, we can animate the moving of items between 2 lists as follows:

App.svelte :

<script>
  import { cubicOut } from "svelte/easing";
  import { crossfade } from "svelte/transition";

  const [send, receive] = crossfade({
    duration: d => Math.sqrt(d * 200),

    fallback(node, params) {
      const style = getComputedStyle(node);
      const transform = style.transform === "none" ? "" : style.transform;

      return {
        duration: 600,
        easing: cubicOut,
        css: t => `
          transform: ${transform} scale(${t});
          opacity: ${t}
        `
      };
    }
  });

  let uid = 1;

  let todos = [
    { id: uid++, done: false, description: "write some docs" },
    { id: uid++, done: false, description: "eat" },
    { id: uid++, done: true, description: "buy milk" },
    { id: uid++, done: false, description: "drink" },
    { id: uid++, done: false, description: "sleep" },
    { id: uid++, done: false, description: "fix some bugs" }
  ];

  const mark = (id, done) => {
    const index = todos.findIndex(t => t.id === id);
    todos[index].done = done;
  };
</script>

<style>
  #box {
    display: flex;
    flex-direction: row;
  }
</style>

<div id='box'>
  <div>
    Done:
    {#each todos.filter(t => t.done) as todo}
      <div
        in:receive="{{key: todo.id}}"
        out:send="{{key: todo.id}}"
      >
        {todo.description}
        <button on:click={mark(todo.id, false)}>Not Done</button>
      </div>
    {/each}
  </div>
  <div>
    Not Done:
    {#each todos.filter(t => !t.done) as todo}
      <div
        in:receive="{{key: todo.id}}"
        out:send="{{key: todo.id}}"
      >
        {todo.description}
        <button on:click={mark(todo.id, true)}> Done</button>
      </div>
    {/each}
  </div>
</div>

In the code above, we created our send and receive transition functions with the crossfade function.

The fallback animation is used when there’s no receive counterpart for the send and vice versa, which can happen if we aren’t moving an element between 2 elements.

The fallback animation just does some scaling and opacity changes to the element being animated.

We render the 2 todo lists for done and not done items. When we click the Done or Not Done button beside the todo item, we’ll see a fade effect as the item is moved between the containing divs.

This is achieved with the in and out animations, we pass in an object with the key property with the value set to the id so that Svelte knows which element we’re moving.

Conclusion

We can use crossfade to coordinate transitions when it involves multiple elements.

Also, we use local transitions to animate list items that are being rendered.

Finally, we can track transition effects by listening to the events that are emitted during transitions by attaching event listeners to each.

Categories
Svelte

Add Transition Effects to a Svelte App

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to add transition effects to a Svelte app.

Transitions

Svelte has built-in libraries for adding transitions. We can add the fade function to create a fade effect.

To use it, we write the following code:

App.svelte :

<script>
  import { fade } from "svelte/transition";
  let visible = true;
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p transition:fade>foo</p>
{/if}

In the code above, we toggle visible between true and false as we click the Toggle button.

Then the p element fades as it’s being toggle on and off.

Transition functions can accept parameters. For instance, we can modify the default options of the fly effect as follows:

App.svelte :

<script>
  import { fly } from "svelte/transition";
  let visible = true;
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p transition:fly="{{ y: 200, duration: 3000 }}">foo</p>
{/if}

The code above moved the word foo up and down as we click the Toggle button. y indicates the distance that we move the word foo up and down. duration is the length of the transition effect.

We should therefore then see the word foo move up and down as we click the Toggle button.

With Svelte, we can also only add transition effects when an item is added to the DOM with the in directive, and animate when the item is removed from the DOM with the out directive.

For instance, we can write the following:

App.svelte :

<script>
  import { fade } from "svelte/transition";
  let visible = true;
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p in:fade="{{ y: 200, duration: 3000 }}">foo</p>
{/if}

The code above only applies the fade effect when the p element with word foo is being inserted into the DOM. We should see a fade effect at that time.

Likewise, we can use the out directive to only add transition effect when the p element is removed from the DOM as follows:

<script>
  import { fade } from "svelte/transition";
  let visible = true;
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p out:fade="{{ y: 200, duration: 3000 }}">foo</p>
{/if}

Creating our own Transition

We can create our own transitions if we don’t want to use the built-in transitions.

For instance, we can write the following code to create an effect that spins the text and change the color as the p element is being removed from the DOM a follows:

<script>
  import { fade } from "svelte/transition";
  import { elasticOut } from "svelte/easing";
  let visible = true;

  const spin = (node, { duration }) => {
    return {
      duration,
      css: t => {
        const eased = elasticOut(t);

        return `
            transform: scale(${eased}) rotate(${eased * 1080}deg);
            color: hsl(
              ${~~(t * 360)},
              ${Math.min(100, 2000 - 1000 * t)}%,
              ${Math.min(50, 2000 - 500 * t)}%
            );`;
      }
    };
  };
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p out:spin="{{ y: 200, duration: 3000 }}">foo</p>
{/if}

Add we did was return the CSS for styling the p element as the time t is increasing.

elasticOut defines our easing, which is our rate of change over time. We passed it into transform to change the size of the text. We also used it to change the rotation angle of the text as it fades out.

JavaScript Animation

We can also animate text with JavaScript. For instance, we can create a typewriter effect as follows:

<script>
  let visible = true;

  const typewriter = (node, { speed = 50 }) => {
    const valid =
      node.childNodes.length === 1 && node.childNodes[0].nodeType === 3;

    if (!valid) {
      return;
    }

    const text = node.textContent;
    const duration = text.length * speed;

    return {
      duration,
      tick: t => {
        const i = Math.ceil(text.length * t);
        node.textContent = text.slice(0, i);
      }
    };
  };
</script>

<button on:click={() => visible = !visible}>Toggle</button>
{#if visible}
<p in:typewriter>Hello Jane. How are you?</p>
{/if}

In the code above, we created the typewriter effect by defining a typewriter function.

We progressive append the text content into the p element as time goes on, which means t is increasing.

Therefore, we’ll get a typewriter when we toggle the text on by clicking the Toggle button since we have:

<p in:typewriter>Hello Jane. How are you?</p>

in the markup.

Conclusion

Svelte has built-in libraries for creating transitions and animations. It makes animation effects easy.

We just import the functions for animation and apply them with in , out , or transition directives.

Also, we can make our own custom animations by writing our own animation functions applying them with the same directives.

Categories
Svelte

Animating Value Changes with Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to create tweens to animate values in a Svelte component.

Creating Animation for Value Changes with tween

We can use the tweened function to create a value that’s animated when we change it.

For instance, we can use it to create an animated progress bar which has an easing effect as follows:

<script>
  import { tweened } from "svelte/motion";
  import { cubicOut } from "svelte/easing";

  const progress = tweened(0, {
    duration: 400,
    easing: cubicOut
  });

  const moveProgress = () => {
    $progress = 0;
    const interval = setInterval(() => {
      $progress += 0.1;
      if ($progress === 1) {
        clearInterval(interval);
      }
    }, 1500);
  };
</script>

<style>
  progress {
    display: block;
    width: 100%;
  }
</style>

<progress value={$progress}></progress>
<br>
<button on:click="{moveProgress}">
  Start
</button>

In the code above, we have the moveProgress function, which sets the $progress store’s value.

Then we update it until it gets to 1. Once it’s 1, then we clear the timer object to stop it from running.

The $progress store is created from the tweened function, which enables us to animate as the value is being changed. It has the initial value of 0 as we passed 0 into the tweened function as the first argument.

The 2nd argument has the object to specify how the animation is done. We set the duration of the animation to 400ms. Then we set the easing function to cubic out.

Cubic out means that the animation moves fast and then slowly.

All options that are available in the object we pass into the 2nd argument are:

  • delay — milliseconds before the tween starts
  • duration — either the duration of the tween in milliseconds or a (from, to) => milliseconds function allowing us to specify the tween
  • easing — a p => t function
  • interpolate — a custom (from, to) => t => value function for interpolating between arbitrary values. Svelte interpolates between numbers, dates, and identically shaped arrays and objects by default

We can pass the options to the progress.set and progress.update as the second argument to override the defaults.

The set and update methods both return a promise that resolves when the tween completes.

spring Function

We can use the spring function to create a store that has values that’s animated as they’re changed.

For instance, we can write the following code to add a spring effect to our square as we move it by hover the mouse on different location:

App.svelte :

<script>
  import { spring } from "svelte/motion";

  let coords = spring({ x: 50, y: 50 });
  let square;

  const setCoords = e => {
    coords.set({ x: e.clientX, y: e.clientY });
    square.style.top = `${$coords.y}px`;
    square.style.left = `${$coords.x}px`;
  };
</script>

<style>
  .screen {
    width: 100vw;
    height: 100vh;
  }

  .square {
    width: 20px;
    height: 20px;
    background-color: red;
    position: absolute;
  }
</style>

<div class="screen" on:mousemove="{setCoords}">
  <div class="square" bind:this={square}>
  </div>
</div>

In the code above, we bind the div with class square to the square variable.

Then we added a setCoords function to set the coords store with the x and y values of the mouse hover position.

We then used that to set the top and left values of the div with class square, which when combined with the existing styles, will move the square.

The spring function gives the square a springy effect as we move the red square around with the mouse.

If we switch between spring and writable , we’ll notice a difference in how the square moves.

Conclusion

We can create animation from store states with the tween and spring functions.

Then tween function is good for values that change less frequently and spring is good for animations of values that change more frequently.

Categories
Svelte

Storing Shared State in a Svelte App

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to store shared states in a Svelte store.

Writable Stores

We can add a writable store to store data that are shared by multiple components.

For instance, we can write the following to create a store and use it in a component:

App.svelte :

<script>
  import { count } from "./stores.js";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };
</script>

<button on:click={increment}>Increment</button>
<p>{countValue}</p>

stores.js :

import { writable } from "svelte/store";

export const count = writable(0);

In the code above, we created a shared writable state in stores.js called count .

Then we subscribed to it to get the latest value and set it to count_value .

We can also call the set method on count to reset the value as follows:

App.svelte :

<script>
  import { count } from "./stores.js";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };

  const reset = () => {
    count.set(0);
  };
</script>

<button on:click={increment}>Increment</button>
<button on:click={reset}>Reset</button>
<p>{countValue}</p>

In the code above, we run count.set(0) when we click on Reset to set the count state to 0.

We have to call unsubscribe on the store when the component is destroyed so that we can free up the resources used for the subscription of states.

For instance, we can modify the example above as follows to do that:

App.svelte :

<script>
  import { count } from "./stores.js";
  import { onDestroy } from "svelte";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };

  onDestroy(unsubscribe);
</script>

<button on:click={increment}>Increment</button>
<p>{countValue}</p>

The code above calls unsubscribe the function in the destroy portion of the component lifecycle so that the count state is no longer subscribed to.

We can replace the store state subscription with the $ . For instance, we can shorten the above example to:

<script>
  import { count } from "./stores.js";
  import { onDestroy } from "svelte";

  const increment = () => {
    count.update(n => n + 1);
  };
</script>

<button on:click={increment}>Increment</button>
<p>{$count}</p>

We eliminated the code to subscribe and unsubscribe from the count store and the component still works the same as before.

Read Only Stores

We can make a store that’s read only with the readable function.

For instance, we can write the following code to make a counter state that’s subscribed to by a component as follows:

stores.js :

import { readable } from "svelte/store";

export const count = readable(0, set => {
  let count = 0;
  const interval = setInterval(() => {
    set(count);
    count++;
  }, 1000);

  return () => {
    clearInterval(interval);
  };
});

App.svelte :

<script>
  import { count } from "./stores.js";
</script>

<p>{$count}</p>

In the code above, we call the readable function in the store. In the second argument, we pass in a callback with the set function as the parameter, which is called to update the store’s value.

We did that in the setInterval callback. Then we return another function, which is used to clean up the code when the store is unsubscribed to.

When we run the code, we see a number that’s incremented by 1 every second on the screen.

Store with Derived State

We can call the derived function to create a store that’s derived from another state.

For instance, we can create a store that emits a value that’s double the count state that we have above as follows:

stores.js :

import { readable, derived } from "svelte/store";

export const count = readable(0, set => {
  let count = 0;
  const interval = setInterval(() => {
    set(count);
    count++;
  }, 1000);

  return () => {
    clearInterval(interval);
  };
});

export const doubleCount = derived(count, $count => $count * 2);

App.svelte :

<script>
  import { count, doubleCount } from "./stores.js";
</script>

<p>{$count}</p>
<p>{$doubleCount}</p>

In the code above, we have the doubleCount store, which is created by calling the derived function with the count store passed in as the first argument and a callback which returns a value that we want to derive from the value emitted by count as the second argument.

Then we see a number that’s incremented by 1 every second and another that’s incremented by 2 every second.

Store Bindings

We can bind directly to the value of the store if the store is writable.

For instance, we can use bind:value to bind to the store to the inputted value directly and manipulate the store’s value as follows:

store.js :

import { writable } from "svelte/store";

export const name = writable("");

App.svelte :

<script>
  import { name } from "./stores.js";
</script>

<input bind:value={$name}>
<button on:click="{() => $name += '.'}">
  Add period
</button>
<p>{$name}</p>

In the code above, we change the value of $name as we type in something in the input, and also when we click Add period.

Then we display the $name store’s value in the p element.

Conclusion

A store is handy for storing shared states. We make stores that are read-only, writable or are derived from another store.

Categories
Svelte

Svelte Component Lifecycle Hooks

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at the lifecycle hooks of a Svelte component.

Component Lifecycle

Every Svelte component has a lifecycle. It starts when it’ created and ends when it’s destroyed.

There’re a handful of functions that allows us to run code at key moments of the lifecycle.

The most frequently used is onMount . It’s run after the component is first rendered to the DOM.

We can add a onMount handler to our component as follows:

<script>
  import { onMount } from "svelte";

  let joke = { value: { joke: "" } };

  onMount(async () => {
    const res = await fetch(`https://api.icndb.com/jokes/20`);
    joke = await res.json();
  });
</script>

<p>{joke.value.joke}</p>

The code above calls the Chuck Norris API to get a joke when the component loads.

Then we display it after it’s loaded.

onDestroy

onDestroy ‘s callback is called when the when the component is being destroyed.

Cleanup code and anything else that runs when the component can go into onDestroy ‘s callback.

For instance, we can write:

<script>
  import { onDestroy } from "svelte";

  let seconds = 0;
  const interval = setInterval(() => seconds++, 1000);

  onDestroy(() => clearInterval(interval));
</script>

<p>Seconds: {seconds}</p>

In the code above, we call setInterval , which returns an interval ID object which we set as the value of interval .

Then when the component is being destroyed, we call clearInterval to clear the interval object from memory by passing interval into clearInterval in the onDestroy callback.

beforeUpdate and afterUpdate

beforeUpdate runs immediately before the DOM has been updated.

afterUpdate is used for running code once the DOM is in sync with our data.

For instance, if we want to scroll to the bottom of the div if the div overflows the window height, we can write the following code:

<script>
  import { beforeUpdate, afterUpdate } from "svelte";
  let div;
  let autoscroll;
  let arr = [];
  setTimeout(() => {
    arr = new Array(100).fill("foo");
  }, 100);

  beforeUpdate(() => {
    autoscroll = div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20;
  });

  afterUpdate(() => {
    if (autoscroll) {
      window.scrollTo(0, div.scrollHeight);
    }
  });
</script>

<div bind:this={div}>
{#each arr as a}
<p>{a}</p>
{/each}
</div>

The code:

div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20

checks if the height of the div is bigger than the scroll height, which includes the height that’s not visible on the screen.

Then in the afterUpdate callback, we can scroll to the bottom of the div if there’s overflow as returned by the boolean above.

Tick

tick is a function that returns a promise that resolves as soon as any pending state changes have been applied to the DOM.

When the component state is invalidated in Svelte, it doesn’t update the DOM immediately. It waits until the next microtask to see if there’re any changes that need to be applied.

This allows the batching of updates.

When we select a range of text in the text area and hit the tab key to toggle its case, then the current selection is cleared and the cursor jumps to the end.

We can retain the selection after the tab key is pressed by in a Textarea by writing the following code:

<script>
  import { tick } from "svelte";

let text = "";
  async function handleKeydown(event) {
    if (event.which !== 9) return;
    event.preventDefault();

    const { selectionStart, selectionEnd, value } = this;
    const selection = value.slice(selectionStart, selectionEnd);

    const replacement = /[a-z]/.test(selection)
      ? selection.toUpperCase()
      : selection.toLowerCase();

    text =
      value.slice(0, selectionStart) + replacement + value.slice(selectionEnd);
    await tick();
    this.selectionStart = selectionStart;
    this.selectionEnd = selectionEnd;
  }
</script>

<textarea bind:value={text} on:keydown={handleKeydown}></textarea>

In the code above we have the handleKeydown to see if the tab key is pressed.

We keep the text from changing by setting the text to the same content as before.

If it is, then we set the selectionStart and selectionEnd after tick is called to retain the selection.

Conclusion

We can use the lifecycle methods to run the code we want during a stage of the lifecycle.

There’s one for when the component loaded, one for when a component is destroyed, 2 for DOM updates, and one for when the component’s state is updated.