Improve UX with Swiping Tab Bar Using Onsen UI for Vue

Vue’s declarative nature in action

Swiping Tab Bar

Cordova apps and Progressive Web Apps targeting mobile devices are very trendy nowadays. Delivering a native look and feel is key to improve user experience and engagement but is not always an easy task. Sure thing, creating styled checkboxes and radio buttons is not an issue at all but the real features that provide the quality jump we seek are based on user interaction.

A swiping tab bar splits the content into different pages and allows the user bringing the desired page into view with his/her own finger. What if, at the same time as the user drags the page, the app gradually changes its appearance with it? Does it sound cool but hard? Let’s see how simple it actually is with Vue.js.

Getting Started

First of all, we need an actual swiping tab bar component. There is a bunch of alternatives out there offering different features. Here, we’ll use the tab bar provided by Onsen UI, which allows performing custom actions while swiping. In case you don’t know about it, Onsen UI has a set of iOS and Android components for Vue apps. For existing projects, it can be installed with NPM or Yarn:

$> npm install onsenui vue-onsenui --save-dev
$> yarn add onsenui vue-onsenui -D

Some necessary files must be included in the app:

import 'onsenui/css/onsenui.css'; // Webpack CSS import
import 'onsenui/css/onsen-css-components.css'; // Webpack CSS import
import VueOnsen from 'vue-onsenui';
Vue.use(VueOnsen);

Otherwise, new projects can be started right away via Vue CLI. It can optionally add Vuex and some other features:

$> vue init OnsenUI/vue-cordova-webpack # For Cordova apps
$> vue init OnsenUI/vue-pwa-webpack # For PWA

After this, <v-ons-tabbar> (among other components) should be available in the app.

Swiping Tab Bar in Vue

Adding a minimum swiping tab bar is very simple in Vue templates with Onsen UI. It can be defined as follows:

<v-ons-tabbar swipeable :tabs="tabs" />

The swipeable attribute can be toggled to allow or avoid swipes at different moments of the app, if necessary. tabs is a simple array of objects describing every tab appearance and content with page, label and icon properties. A full reference page for this component can be found here.

On top of this, more props can be used to modify the default behavior, add some extra customization and obtain a unique app style. Let’s see, for example, how the on-swipe prop can be used to gradually change the interface colors while swiping in order to differentiate sections within the app (link to the real app at the end of the article).

Color Interpolation

Code snippet:

This code creates a page with a simple toolbar and tab bar using v-ons-page, v-ons-toolbar and v-ons-tabbar components. The tabs property contains an array of tabs. Both page and label properties will be used by the tab bar component itself to describe the tab content and appearance, but it doesn’t stop us from providing custom properties like theme or anything else. These themes are simple arrays of RGB colors. We’ll see in a moment why this format is good to describe themes.

Notice how theswipeTheme computed property is passed to the toolbar (via style prop) and the tab bar (via tabbar-style prop). Whenever this property is changed, the style of those two components will be updated. There is also the onSwipe method provided in on-swipe prop, which will be called whenever the user moves the finger on the screen. But how do we now change the interface colors appropriately?

Linear Interpolation

Simply put, a linear interpolation (“lerp” in computer graphics) is a formula that generates middle points between two data values (like colors) based on some input. Say, for instance, we want to move a dot on the screen from an origin point x0 to an end point x1 gradually. We just need to provide both points to the formula together with the “completion ratio” in order to get the position where the dot should be placed in a specific instant. In other words, a ratio (or alpha value) describing how close we are to reach the end point.

const lerp = (x0, x1, r) => (1 — r) * x0 + r * x1;

For example, if we want to generate 3 points from x0 to x1 (excluding both of them), the first step will need r = 0.25 (25% complete); the second step r = 0.5 (50% complete, i.e. in the middle); and the third step r = 0.75 (75% complete). Of course, it can generate as many middle points as we want by just providing different ratios.

This is not only applicable to physical distances. In the previous code, we want to gradually transform a color into another based on the swipe position. In order to achieve this, we need to represent colors as discrete values and know the swipe ratio between two pages. Precisely, RGB colors are composed of 3 values that can be separately interpolated. Other representations can also be interpolated but they imply more complex code.

Apart from that, the swiping tab bar component provides the decimal index of the current page in onSwipe hook. An index of 1.65, for instance, means that the current swipe is 65% of the way between page 1 and page 2 (r = 0.65).

Therefore, if a different RGB color array is assigned to each tab, we’ll be able to interpolate each of these values based on the current swipe:

onSwipe(index, animationOptions) {
this.animationOptions = animationOptions;
  const x0 = Math.floor(index),
x1 = Math.ceil(index),
ratio = index % 1;
  this.colors = this.colors.map((c, i) =>
lerp(
this.tabs[x0].theme[i],
this.tabs[x1].theme[i],
ratio
)
);
}

This assigns a new color array to this.colors, which is reactively used by this.swipeTheme computed property to create valid CSS syntax based on these colors.

Notice that animationOptions is also provided as the second parameter. It will be empty while swiping but, when releasing the swipe, the tab bar will use the finger velocity to finish the swipe animation. This velocity is given as duration and timing (Cubic Bezier curves) in this parameter and can be used to create a CSS transition. This way, all the animations (pages, tab bar border, and colors) will be synchronized.

What’s Next

As you can see, Vue’s declarative nature keeps all of this really simple. We can just update a specific property instead of grabbing elements from the DOM and changing styles manually.

A complete Cordova app with the previous (and more) code can be found here. It adds more interpolations following the same concept. In case you want to know more about Onsen UI for Vue, have a look at the official site.

I invite you to try out different animations and share them with us on Twitter :)

Happy coding!