Categories
Vue

Adding Getters to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add getters to a Vuex store to add states derived from other store states.

Adding Getters to Our Store

We can add a getter to our Vuex store by adding a getters property as follows:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done);
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: {
    doneTodos() {
      return this.$store.getters.doneTodos;
    },
    ...Vuex.mapState({
      todos: "todos"
    })
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <div>
        <p>Todos:</p>
        <div v-for="todo of todos">{{todo.text}}</div>
        <p>Done Todos:</p>
        <div v-for="todo of doneTodos">{{todo.text}}</div>
      </div>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code, we have a getters object:

getters: {
  doneTodos: state => {
    return state.todos.filter(todo => todo.done);
  }
}

that derives the state from the todos state.

Then we accessed doneTodos getter by writing:

computed: {
  doneTodos() {
    return this.$store.getters.doneTodos;
  },
  ...Vuex.mapState({
    todos: "todos"
  })
}

in the Vue instance.

Then the items are rendered in our template and we get:

Todos:

todo1
todo2
Done Todos:

todo1

displayed.

Method-Style Access

We can return a function in a getter function so that we can call the getters from a component with one or more arguments.

For instance, if we want to make a getter that gets a todo by ID, we can write the following:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    getTodoById: state => id => {
      return state.todos.find(todo => todo.id === id);
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapState({
    todos: "todos"
  }),
  methods: {
    getTodoById(id) {
      return this.$store.getters.getTodoById(id);
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      {{getTodoById(1).text}}
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we see todo1 displayed on the screen.

We have the following code to define our getter:

getters: {
  getTodoById: state => id => {
    return state.todos.find(todo => todo.id === id);
  }
}

The code above returns a function and then returns a todo with the given ID.

Then we access it in our Vue instance by defining the getTodoById method as follows:

getTodoById(id) {
  return this.$store.getters.getTodoById(id);
}

Finally, we render it in our template by writing:

{{getTodoById(1).text}}

The mapGetters Helper

We can use the mapGetters helper to map our getters to computed properties as follows:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    doneTodosCount: state => {
      return state.todos.filter(todo => todo.done).length;
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapGetters(["doneTodosCount"])
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      {{doneTodosCount}}
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we get 1 displayed since we mapped doneTodosCount getter to a computed property with the same name with:

computed: Vuex.mapGetters(["doneTodosCount"])

Conclusion

We can use getters to add states that are derived from another state.

To define a getter, we add a method to the getters property that returns something derived from the state or a function that derives something from a state.

To include it in our components, we can use the this.$store.getters method or use the mapGetters method.

Categories
Vue

Add Simple State Management with Vuex

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add Vuex to our app and add a simple store.

What is Vuex?

Vuex is for storing states that are needed by multiple components in one central location.

It lets us get and set shared state and propagate any changes made to the shared state automatically to all components.

Courtesy of https://vuex.vuejs.org/

In the workflow diagram above, we can see that Vuex mutations are committed by our code when we get something from the back end API.

The mutation will update the state with the back end API data and the state will be updated in our Vue components.

We can also dispatch mutations from Vue components to change the Vuex store state the change will be propagated to all components that have access to the store.

Getting Started

We can include Vuex with a script tag in our HTML code:

<script src="https://unpkg.com/vuex"></script>

Then we can create a simple store by using the Vuex.Store as follows:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

The code above is a store which stores the state count .

Then we can commit the increase mutation by running:

store.commit("increase");

Then we can get the state after the mutation is done by running:

console.log(store.state.count);

store.state.count is the count state from the Vuex store. Then we should see 1 logged.

Getting Vuex State into Vue Components

We can get the state into our store by adding a computed property.

Therefore, to add the state into our store, we can write the following:

index.js :

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

new Vue({
  el: "#app",
  computed: {
    count() {
      return store.state.count;
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <p>{{count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we should see 0 displayed since count is 0 initially in the store .

Then whenever store.state.count updates, the computed property will be updated and the view will update with the new value.

A more convenient way to inject the store into all child components is to add the store in the root component as follows:

index.js :

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

new Vue({
  el: "#app",
  store
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <p>{{$store.state.count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then it’ll be available to all child components and we don’t have to worry about add computed properties for every value.

It works with child component without much changes:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

Vue.component("counter", {
  template: `<div>{{ count }}</div>`,
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
});

new Vue({
  el: "#app",
  store
});

this.$store is available to the counter component just by including it in the root Vue component.

Photo by Clark Street Mercantile on Unsplash

The mapState Helper

To avoid adding a new computed property for every state that’s in the store, we can use the mapState helper to add it. For example, we can write the following code to do that:

index.js :

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

Vue.component("counter", {
  data() {
    return {
      localCount: 1
    };
  },
  template: `
    <div>
      <div>{{ count }}</div>
      <div>{{ countAlias }}</div>
      <div>{{ countPlusLocal }}</div>
    </div>
  `,
  computed: Vuex.mapState({
    count: state => state.count,
    countAlias: "count",
    countPlusLocal(state) {
      return state.count + this.localCount;
    }
  })
});

new Vue({
  el: "#app",
  store
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <counter></counter>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we see:

0

0

1

Because we called mapState as follows:

computed: Vuex.mapState({
  count: state => state.count,
  countAlias: "count",
  countPlusLocal(state) {
    return state.count + this.localCount;
  }
})

We have:

count: state => state.count,

which gets the count state from the store and returns it. The count is 0 so we get 0.

Then we have:

countAlias: "count"

which is a shorthand for:

count: state => state.count

And finally, we have:

countPlusLocal(state) {
  return state.count + this.localCount;
}

which adds state.count from the store to this.localCount , which we set to 1.

Object Spread Operator

We can combine local computed properties with mapState by applying the spread operator to mapState as follows:

index.js :

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

Vue.component("counter", {
  data() {
    return {
      localCount: 1
    };
  },
  template: `
    <div>
      <div>{{ count }}</div>
      <div>{{ foo }}</div>
    </div>
  `,
  computed: {
    foo() {
      return 2;
    },
    ...Vuex.mapState({
      count: "count"
    })
  }
});

new Vue({
  el: "#app",
  store
});

Then we get:

0

2

displayed since foo always returns 2.

Components Can Still Have Local State

Components can still have their own local state, so we don’t have to put everything in the Vuex store.

Conclusion

We can add a Vuex store to our app to store the states of our app that are shared by multiple components.

To make getting state easy, we can include the store in the root Vue component.

Then we call the mapState helper to get the states we want in any component.

We can also combine it with the local states with the spread operator in the computed object.

Categories
Vue

Adding Modules to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add modules to separate a Vuex store into smaller parts.

Dividing a Store into Modules

Vuex uses a single state tree. This means the states are located in one big object. This will be bloated is our app grows big.

To make a Vuex store easier to scale, it can be separated into modules. Each module can have its own state, mutations, getters, and actions.

The state parameter in mutations and getters are the module’s local state.

By default, all actions, mutations, and getters inside modules are registered under a global namespace. This allows multiple modules to react to the same mutation or action type.

We can divide our store into module as in the following example:

index.js :

const moduleA = {
  state: {
    count: 0
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const moduleB = {
  state: {
    count: 1
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
});

console.log(store.state.a.count);
console.log(store.state.b.count);

Then in the console.log output, we should see 0 and 1 since moduleA ‘s initial count state is 0 and moduleB ‘s initial count state is 1.

To make each module self-contained, we have to namespace it by setting the namespaced option to true .

We can namespace a module and then call dispatch on actions after namespacing the modules as follows:

index.js :

const moduleA = {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const moduleB = {
  namespaced: true,
  state: {
    count: 1
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
});

new Vue({
  el: "#app",
  store,
  computed: {
    ...Vuex.mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    increaseA(payload) {
      this.$store.dispatch("a/increase", payload);
    },
    increaseB(payload) {
      this.$store.dispatch("b/increase", payload);
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click="increaseA({amount: 10})">Increase</button>
      <button @click="increaseB({amount: 10})">Increase</button>
      <p>A Count: {{a.count}}</p>
      <p>B Count: {{b.count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have namespaced: true set in each module, and then we added 2 methods to our Vue instance to dispatch the namespaced actions:

increaseA(payload) {
  this.$store.dispatch("a/increase", payload);
}

and:

increaseB(payload) {
  this.$store.dispatch("b/increase", payload);
}

Since we have namespaced set to true , we have to dispatch the actions by passing in “a/increase” and “b/increase” to dispatch .

Then once we clicked the buttons, our methods are called to dispatch the actions and the numbers will increase.

Register Global Action in Namespaced Modules

We can also register global actions in namespaced modules, by setting the root option to true and place the action definition in the function handler.

To do register a global action, we can write something like the following code:

index.js :

const moduleA = {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const moduleB = {
  namespaced: true,
  state: {
    count: 1
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const rootModule = {
  actions: {
    increaseAll: {
      root: true,
      handler(namespacedContext, payload) {
        namespacedContext.commit("a/increase", payload);
        namespacedContext.commit("b/increase", payload);
      }
    }
  }
};

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB,
    root: rootModule
  }
});

new Vue({
  el: "#app",
  store,
  computed: {
    ...Vuex.mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    ...Vuex.mapActions(["increaseAll"])
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click="increaseAll({amount: 10})">Increase All</button>
      <p>A Count: {{a.count}}</p>
      <p>B Count: {{b.count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we added the rootModule which has the global action as follows:

const rootModule = {
  actions: {
    increaseAll: {
      root: true,
      handler(namespacedContext, payload) {
        namespacedContext.commit("a/increase", payload);
        namespacedContext.commit("b/increase", payload);
      }
    }
  }
};

A global action has the root option set to true and a handler method which is used to dispatch actions from any module.

Then in the Vue instance, we have:

methods: {
  ...Vuex.mapActions(["increaseAll"])
}

to map the increaseAll action to a method in the Vue instance.

Then in the template, we have:

<button @click="increaseAll({amount: 10})">Increase All</button>

to call the increaseAll method returned from the mapActions method when the button is clicked.

Then we should both numbers increasing since we mapped both module’s state to the Vue instance’s data.

Dynamic Module Registration

We can also register a module dynamically by using the store.registerModule method as follows:

index.js :

const moduleA = {
  state: {
    count: 0
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increase({ commit }, payload) {
      commit("increase", payload);
    }
  }
};

const store = new Vuex.Store({});

store.registerModule("a", moduleA);

new Vue({
  el: "#app",
  store,
  computed: {
    ...Vuex.mapState({
      a: state => state.a
    })
  },
  methods: {
    ...Vuex.mapActions(["increase"])
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click="increase({amount: 10})">Increase</button>
      <p>A Count: {{a.count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

The store.registerModule takes a string for the module name, and then an object for the module itself.

Then we can call the helpers to map getters to computed properties and actions/mutations to methods. In the code above, we have the increase action mapped to a method.

Then we can call it in our template as usual.

Conclusion

If our Vuex store is big, we can divide it into modules.

We can register modules when we create the store or dynamically with registerModule .

Then we can map actions/mutations by their name as usual, and we can map the state by accessing state.a.count , where a is the module name, and count is the state name. Replace it with our own module and state names if the code is different.

We can also namespace the modules. Then we dispatch the actions starting with the module name and a slash instead of just the name.

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.