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.

Categories
Svelte

Sharing Code Between Svelte Component Instances with Module Context

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 share variables between component instances with module context.

Module Context

Svelte components can contain code that is shared between multiple component instances.

To do this, we can declare the script block with context='module' . The code inside it will run once, when the module first evaluates, rather than when a component is instantiated.

For instance, we can use it to create multiple audio elements that can only have one audio element play at a time by stopping the ones that aren’t being played as follows:

App.svelte :

<script>
  import AudioPlayer from "./AudioPlayer.svelte";
</script>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelts :

<script context="module">
  let current;
</script>

<script>
  export let src;

  let audio;
  let paused = true;

  const stopOthers = () =>{
    if (current && current !== audio) current.pause();
    current = audio;
  }
</script>

<article>
  <audio
    bind:this={audio}
    bind:paused
    on:play={stopOthers}
    controls
    {src}
  ></audio>
</article>

In the code above, we have the AudioPlayer component which has the code that’s shared between all instances in:

<script context="module">
  let current;
</script>

This lets us call pause on any instance of the audio element that isn’t the one that’s being played as we did in the stopOthers function. stopOthers is run when we click play on an audio element.

Then we included the AudioPlayer instances in the App.svelte component.

Therefore, when we click play on one of the audio elements, we pause the other ones.

Exports

Anything that’s exported from a context='module' script block becomes an export of the module itself. For instance, we can create a button to stop all the audio elements in AudioPlayer that is currently playing as follows:

App.svelte :

<script>
  import AudioPlayer, { stopAll } from "./AudioPlayer.svelte";
</script>

<button on:click={stopAll}>Stop All</button>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelte :

<script context="module">
  let elements = new Set();

  export const stopAll = () => {
    elements.forEach(element => {
      element.pause();
    });
  };
</script>

<script>
  import { onMount } from 'svelte';
  export let src;
  let audio;

  onMount(() => {
    elements.add(audio);
    return () => elements.delete(audio);
  });
</script>

<article>
  <audio
    bind:this={audio}
    controls
    {src}
  ></audio>
</article>

In the code above, we add all the audio element instances to the elements set which we created in the contex='module' block as each AudioPlayer component is mounted.

Then we export the stopAll function, which has all the audio element instances from elements , and we call pause on all of them by looping through them and calling pause .

In App.svelte , we import the stopAll function from AudioPlayer and then call it when we click the Stop All button.

Then when we click play on one or more audio elements, they’ll all be paused when we click the Stop All button.

Note that we can’t have a default export because the component is the default export.

Conclusion

We can use the module context script block to add script blocks that are shared between all instances of a component.

If we export functions inside module context script blocks, we can import the function and then call it.

Categories
Svelte

More Complex Bindings 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 use the bind directive outside of input elements.

Media Elements

We can use the bind directive with audio or video elements.

For instance, we can bind to various attributes of audio or video elements as follows:

<script>
let time = 0;
let duration = 0;
let paused = true;
</script>

<video
  bind:currentTime={time}
  bind:duration
  bind:paused
  controls
  src='https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4'
>
</video>
<p>{time}</p>
<p>{duration}</p>
<p>{paused}</p>

In the code above, we used the bind directive to bind to currentTime , duration , and paused properties of the DOM video element.

They’re all updated as the video is being played or paused if applicable.

6 of the bindings for the audio and video elements are read only, they’re:

  • duration — length of the video in seconds
  • buffered — array of {start, end} objects
  • seekable — array of {start, end} objects
  • played — array of {start, end} objects
  • seeking — boolean
  • ended — boolean

2 way binds are available for:

  • currentTime — current point in the video in seconds
  • playbackRate — how fast to play the video
  • paused
  • volume — value between 0 and 1

Video also has the videoWidth and videoHeight bindings.

Dimensions

We can bind to the dimensions of an HTML element.

For instance, we can write the following to adjust the font size of a piece of text as follows:

<script>
  let size = 42;
  let text = "hello";
</script>

<input type=range bind:value={size}>

<div >
  <span style="font-size: {size}px">{text}</span>
</div>

There’re also read-only bindings for width and height of elements, which we can use as follows:

<script>
 let w;
 let h;
</script>

<style>
  div {
    width: 100px;
    height: 200px;
  }
</style>

<div bind:clientWidth={w} bind:clientHeight={h}>
  {w} x {h}
</div>

bind:clientWidth and bind:clientHeight binds the width and height to w and h respectively.

This Binding

We can use the this bind to set the DOM element object to a variable.

For instance, we can use the this binding to access the canvas element as follows:

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

  let canvas;

  onMount(() => {
    const ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.moveTo(20, 20);
    ctx.lineTo(20, 100);
    ctx.lineTo(100, 100);
    ctx.stroke();
  });
</script>

<canvas
  bind:this={canvas}
  width={500}
  height={500}
></canvas>

In the code above, we put our canvas manipulation code in the onMount callback so that we only run the code when the canvas is loaded.

We have bind:this={canvas} to bind to the canvas element so that we can access it in the onMount callback.

In the end, we get an L-shaped line drawn on the screen.

Component Bindings

We can bind to component props with bind .

For example, we can write the following code to get the value from the props as follows:

App.svelte :

<script>
  import Input from "./Input.svelte";
  let val;
</script>

<Input bind:value={val} />
<p>{val}</p>

Input.svelte :

<script>
export let value;
</script>

<input bind:value={value} />

In the code above, we have value bound to the input element. Then since we passed val in as the value of the value prop and used the bind directive, val in App will be updated as value in Input is updated.

So when we type into the input box, the value will also be shown in the p element below.

Conclusion

We can bind to media elements when we use bind with some property names.

Using the this binding, we can get the DOM element in the script tag and call DOM methods on it.

Component props can also be used with bind , then we get 2-way binding between parent and child via the props.