Table of Contents
URL: https://www.progressiverobot.com/ionic-ionic-4-vue-skeleton-text/
When we're loading asynchronous content, it's advised that you show the user some "skeleton" UI that gives the impression of content being loaded. Let's use ion-skeleton-text to show how we'd handle this inside of Ionic!
Inside of an Ionic project we'd traditionally have to accomplish this ourselves with CSS or a third party library. Thankfully, the latest update inside of Ionic 4.1 Hydrogen brings us ion-skeleton-text which we can use to display skeleton content.
Ionic Vue + Skeleton Text
We'll be looking at this with the context of a Vue.js project, but as Ionic is built with Stencil, the underlying principles are framework-agnostic.
To get started, ensure you have Node.js installed on your machine. Then, run the following in your terminal:
$ npm install -g @vue/cli
$ vue create vue-ion-skeleton
> default project setup
$ npm install @ionic/core @ionic/vue
We then need to set up IonicVue inside of our project inside of main.js. We'll also be importing the basic styles that Ionic requires from @ionic/core
[label main.js]
import Vue from 'vue';
import App from './App.vue';
import '@ionic/core/css/core.css';
import '@ionic/core/css/ionic.bundle.css';
import IonicVue from '@ionic/vue';
Vue.use(IonicVue);
Vue.config.productionTip = false;
new Vue({
render : (h) => h(App)
}).$mount('#app');
At this stage, we can test that everything works correctly by creating a bare bones Ionic application inside of App.vue:
[label App.vue]
<template>
<ion-app>
<ion-header>
<ion-toolbar color="danger">
<ion-title>S C A R Y</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card>
<img src="https://images.unsplash.com/photo-1513681955987-968b5455d7d7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=933&q=80"/>
<ion-card-header>
<ion-card-subtitle>Spooky, Scary</ion-card-subtitle>
<ion-card-title>Skeleton</ion-card-title>
</ion-card-header>
<ion-card-content>
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</ion-card-content>
</ion-card>
</ion-content>
</ion-app>
</template>
<script>
export default {
name: 'app',
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Now that we've created a <^>bare bones<^> application, let's move on to our ion-skeleton-text example:
Skeleton UI
For example's sake, let's say we wanted to load numerous spooky todos and each one comes from our API with varying data.
If our user was at a location with bad WiFi (<^>say, a coffee shop like the one I'm in right now<^>), it'd be futile to expect that this data will appear instantaneously.
What do we do? Display skeleton data of course!
Let's implement this inside of our application. We'll use the json-placeholder API to get data from an API and use ion-skeleton-text as a UI buffer until our data arrives.
<template>
<ion-app>
<ion-header>
<ion-toolbar color="danger">
<ion-title>S C A R Y T O D O S</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list v-if="todos.length > 0">
<ion-item v-for="todo in todos" :key="todo.id">
<ion-label>{{todo.title}}</ion-label>
</ion-item>
</ion-list>
<ion-list v-else>
<ion-item v-for="i in 20" :key="i">
<ion-label>
<ion-skeleton-text animated>
</ion-skeleton-text>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</template>
<script>
export default {
name: 'app',
data() {
return {
todos: []
}
},
created() {
setTimeout(
() => (
this.getDataFromAPI()
), 3000)
},
methods: {
async getDataFromAPI() {
try {
const req = await fetch('https://jsonplaceholder.typicode.com/todos')
this.todos = await req.json()
}
catch(e) {
console.error(`Error: ${e}`)
}
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Let's take a deep dive into what's happening here:
- Firstly, we're getting access to a list of
todosby making afetchcall to the json-placeholder API. This is done via thegetDataFromAPImethod that we've created.
- We're then calling the
getDataFromAPImethod inside of thecreatedhook inside of asetTimeoutwith a 3 second delay. This mirrors a moderate speed internet connection and gives us enough time to see our skeleton text in action.
- The use of
ion-skeleton-textinside of ourv-elseblock allows us to use the Ionic component that would otherwise <^>replace<^> the skeleton component, thus, keeping as much as the original styling as possible.
Note: We're taking advantage of the animated attribute within the ion-skeleton-text to animate this on screen. You'll see what it looks like when it's <^>not<^> animated later in this article.
This gives us the following magical piece of UI:
Other Examples
The great thing about the ion-skeleton-text component is that it's super flexible! We can use the width style attribute to change how it looks on screen.
Let's add a little bit of randomness to our ion-skeleton-text:
<ion-list v-else>
<ion-item v-for="i in 20" :key="i">
<ion-label>
<ion-skeleton-text :style="`width: ${(80 + Math.random () * Math.floor(240))}px;`">
</ion-skeleton-text>
</ion-label>
</ion-item>
</ion-list>
Summary
Tada! 🎉 Now we can make awesome "loading" UIs in places where the spinner doesn't make sense. It's a great way to replicate <^>how<^> text-based parts of your components will look white asynchronously loading data.
The source code for this article is available here: Source code