top

Writing a Javascript tweening engine Between.js

IntroductionTo be honest, there are already tons of tweening engines in JavaScript ecosystem, there are great (popular) ones, such as Tween.js and GSAP.There are PROS and CONS for both as I’ve loved the simplicity & accessibility of Tween.js and performance/smooth of GSAP. However, the web is moving forward and libraries need to be upgraded to meet today's conditions.A month ago I decided to try my skills in writing own tweening engine i.e Between.js and here’s how I did it…Preparing and SetupEvery modern library needs to be surrounded with great tools from JavaScript ecosystem, such as linting, unit testing, tests coverage. Ideally, I would either include benchmarks and more visual examples, but I’d say they are rather incomplete at the moment of writing this article.Here is a list of great tools I’ve used for project maintenance:Ava.js — Futuristic JavaScript test runner Istanbul.js — Tests coverage analysis tool XO — JavaScript happiness style linterEvery single line of open source repository should be limited & tested to follow certain rules and result in a great piece of software for further usage by millions. That’s why I love to introduce these tools that will help you automate & secure your development workflow.For running tests/coverage remotely we use Travis CI, which is widely used among the open source community. Here’s how it looks like https://travis-ci.org/sasha240100/between.jsConception & APIThe most important part and the most interesting at the same time is API development. Start with doing a research, look at existing implementations of similar open source software and make sure that you really need to create something new  rather than using great and well-trusted tools.In my case, I just wanted a proper Tween.js replacement written in ES6 with middleware support. That is what pushed me to prepare and set up a Between.js.                                                                                   Standard Between.js APIEverything is that simple. Yes, it looks similar to Tween.js/GSAP just because this piping pattern is great. And there is nothing bad about it.So what was wrong(not so good) with Tween.js? For me (personally) I would highlight the necessity of using objects. The only structure you could interpolate/tween was an object.In between.js we decided to tween literally everything. This way you can interpolate an object, array, number or even a string (with a help of plugins). Yes, you can tween from rgb(255, 0, 0) to rgba(65, 255, 134, 0.5) for example.P.s.: …and even to hex value, such as #008B8B which is a “Cyan 4” color according to color-hex.com.The magic above can be done with a help of dom-color.js plugin for between.js. We’ll cover this topic again closer to the end of the article.EasingOn some point of writing between.js, we thought that it would be great to make a powerful tweening API, that is simple in usage. Below is a basic easing code sample:                                                  Basic easing usage example                                                    Easing in actionconst value = document.querySelector('[data-value]'); const state = document.querySelector('[data-state]'); function createTween() {   new Between(1, 10)     .time(4000)     .easing(Between.Easing.Cubic.InOut)     .on('update', v => {     value.innerText = `value: ${v.toFixed(2)}`;   }).on('start', () => {     state.innerText = 'state: running';   }).on('complete', () => {     state.innerText = 'state: complete';   }); } window.onload = () => setTimeout(createTween, 2000);We used an easing-functions package and expose its functions through the Between. Easing object. So if you mind expanding the easing collection, please contribute to that package and we’ll update its version in the dependencies to include more varieties of easings. At the moment we support 31 easing modes:                                                                              Easing — functions modesDepending on your skills and experience easing API can be a powerful instrument you can try in your future projects, just, for example, an object transforms transition:                                        Easing animation transition example.const element = document.querySelector('#rocket'); const state = document.querySelector('[data-state]'); function createTween() {   const from = {x: 0, y: 0, r: 0};   const to = {x: 300, y: -200, r: -40};     new Between(from, to)     .time(3000)     .easing(Between.Easing.Cubic.InOut)     .on('update', v => {     element.style.transform =       `translate(${v.x}px, ${v.y}px) rotate(${v.r}deg)`;   }).on('complete', () => {     layDown();   }); } function layDown() {   const from = {x: 300, y: -200, r: -40};   const to = {x: 350, y: -200, r: -80};     new Between(from, to)     .time(1000)     .easing(Between.Easing.Quadratic.InOut)     .on('update', v => {     element.style.transform =       `translate(${v.x}px, ${v.y}px) rotate(${v.r}deg)`;   }).on('complete', () => {     new Between(0, 100)     .easing(Between.Easing.Cubic.InOut)     .time(900).on('update', v => {       element.style.filter = `grayscale(${v}%)`;     });   }) } window.onload = () => setTimeout(createTween, 2000); A few days ago a GitHub contributor added an another feature — “pausing tweens” pause(), play(), isPaused() methods. These can be used for dynamic interaction with tweens and expands between.js possibilities in usage by video games and creative websites. Below is a simple example of how it can be used:const state = document.querySelector('#tweeningState'); document.querySelector('#playPauseButton')   .addEventListener('click', toggle()); let between = new Between(0, 400)   .time(2000)   .on('pause', () => {     state.innerText = 'state: paused';   }).on('play', () => {     state.innerText = 'state: running';   }); function toggle() {   if (between.isPaused)     between.play();   else       between.pause(); }                                                        Pausing/resuming a tween const element = document.querySelector('#move'); const state = document.querySelector('[data-state]'); let between; function createTween() {   between = new Between(0, 400)     .time(2000)     .easing(Between.Easing.Cubic.InOut)     .on('update', v => {     element.style.transform = `translateX(${v}px)`;   }).on('start', () => {     state.innerText = 'state: running';   }).on('pause', () => {     state.innerText = 'state: paused';   }).on('play', () => {     state.innerText = 'state: running';   }).on('complete', () => {     state.innerText = 'state: complete';   }); } function toggle() {   if (!between) return;   if (between.isPaused) {     between.play();   } else {     between.pause();   } } Color pluginWhen Between.js was almost ready we decided to develop an additional color plugin, that provides a comfortable color change way.Color types that are supported by the plugin:RGB or RGBAHEXHSLKeywords (red, green…)                                                           Node/Webpack color plugin usage example                                                         Color plugin in actionconst value = document.querySelector('[data-value]'); const state = document.querySelector('[data-state]'); function createTween() {   new Between('green', 'rgb(255, 0, 0)')     .time(5000)     .easing(Between.Easing.Cubic.InOut)     .on('update', v => {     value.innerText = `Color: ${v}`;     value.style.color = v;   }).on('start', () => {     state.innerText = 'state: running';   }).on('complete', () => {     state.innerText = 'state: complete';   }); } window.onload = () => setTimeout(createTween, 2000); What’s inside of a Color plugin?We used the following dependencies:1.color — A nice library that was used for color conversion inside of initialize() functionJavaScript library for immutable color conversion and manipulation with support for CSS color strings.2.color-string — We used it to check if a string is a color keyword. Either is a dependency of a library above.library for parsing and generating CSS color strings.3.lerp — A neat linear interpolation function, written by Matt DesLauriers.The implementation uses Plugins API and includes the following functions inside of a plugin object:Testing if the startValue should be covered by the color plugin or nottest(startValue) { // rgb(255, 0, 0)   return startValue.indexOf('rgb') >= 0     || startValue.indexOf('#') >= 0     || startValue.indexOf('hsl') >= 0     || (typeof startValue === 'string' && colorString.get.rgb(startValue)); // true } Initializing the startValue and destValue. And it does an immediate conversion from color strings to arrays of format [r, g, b] that can be interpolated easily.initialize(startValue, destValue) {   return {     data: {       format: (startValue.indexOf('rgba') >= 0 && 'rgba')        || (startValue.indexOf('rgb') >= 0 && 'rgb')        || (startValue.indexOf('#') >= 0 && 'hex')        || Color(startValue).model     },     startValue: Color(startValue).rgb(),     destValue: Color(destValue).rgb()   } }Color arrays interpolation. This step is executed inside of update()function of a tween object. The output is converted back to string and is stored in this.valueinterpolate(startValue, destValue, progress, data) {   const r = lerp(startValue.color[0], destValue.color[0], progress);   const g = lerp(startValue.color[1], destValue.color[1], progress);   const b = lerp(startValue.color[2], destValue.color[2], progress);   const a = lerp(startValue.valpha, destValue.valpha, progress);   const color = Color.rgb(r, g, b, a)[data.format === 'rgba' ? 'rgb' : data.format]();   return typeof color === 'string' ? color : color.string(); }IntegrationSo let’s consider we are all webpack users and interested in the dom-color plugin and we want to include it the usual way — with harmony modules. It’s a little bit more tricky than in a browser but turns out it is just as simple as it can be. The only thing we have to do is to import a ColorPlugin (as default) and assign it to internal Between._plugins object. The property name doesn’t matter, but I advise you to keep its name related to avoid further confusions.                                                         Color & easing integration                                                             Color & easing integration in actionMore about integration can be found in the documentation on GitHub.To sum up……it was an interesting experience for us to create something new, redesign old but gold patterns to follow modern ES6 trends and expand functionality with plugin support integration.We really want you to try between.js in action and join our community to support open source software development, so please open a new issue and propose your ideas. We are open to bringing some more plugins to live soon!Big thanks to Alexandr Kornienko for a collaboration, he helped me to prepare this article and developed some cool visual demos for easing.
Rated 4.5/5 based on 11 customer reviews
Normal Mode Dark Mode

Writing a Javascript tweening engine Between.js

Alexander Buzin
Blog
27th Sep, 2018
Writing a Javascript tweening engine Between.js

Introduction

To be honest, there are already tons of tweening engines in JavaScript ecosystem, there are great (popular) ones, such as Tween.js and GSAP.

There are PROS and CONS for both as I’ve loved the simplicity & accessibility of Tween.js and performance/smooth of GSAP. However, the web is moving forward and libraries need to be upgraded to meet today's conditions.

A month ago I decided to try my skills in writing own tweening engine i.e Between.js and here’s how I did it…


java


Preparing and Setup

Every modern library needs to be surrounded with great tools from JavaScript ecosystem, such as linting, unit testing, tests coverage. Ideally, I would either include benchmarks and more visual examples, but I’d say they are rather incomplete at the moment of writing this article.

Here is a list of great tools I’ve used for project maintenance:

  • Ava.jsFuturistic JavaScript test runner
  • Istanbul.js— Tests coverage analysis tool
  • XO— JavaScript happiness style linter


Every single line of open source repository should be limited & tested to follow certain rules and result in a great piece of software for further usage by millions. That’s why I love to introduce these tools that will help you automate & secure your development workflow.
For running tests/coverage remotely we use Travis CI, which is widely used among the open source community. Here’s how it looks like 
https://travis-ci.org/sasha240100/between.js

Conception & API
The most important part and the most interesting at the same time is API development. Start with doing a research, look at existing implementations of similar open source software and make sure that you really need to create something new  rather than using great and well-trusted tools.
In my case, I just wanted a proper Tween.js replacement written in ES6 with middleware support. That is what pushed me to prepare and set up a 
Between.js.

     Standard Between.js API

                                                                                   Standard Between.js API

Everything is that simple. Yes, it looks similar to Tween.js/GSAP just because this piping pattern is great. And there is nothing bad about it.

So what was wrong(not so good) with Tween.js? For me (personally) I would highlight the necessity of using objects. The only structure you could interpolate/tween was an object.

In between.js we decided to tween literally everything. This way you can interpolate an object, array, number or even a string (with a help of plugins). Yes, you can tween from rgb(255, 0, 0) to rgba(65, 255, 134, 0.5) for example.

P.s.: …and even to hex value, such as #008B8B which is a “Cyan 4” color according to color-hex.com.

The magic above can be done with a help of dom-color.js plugin for between.js. We’ll cover this topic again closer to the end of the article.


Easing

On some point of writing between.js, we thought that it would be great to make a powerful tweening API, that is simple in usage. Below is a basic easing code sample:       Basic easing usage example

                                                  Basic easing usage example


                                                    Easing in action

const value = document.querySelector('[data-value]'); 
const state = document.querySelector('[data-state]'); 

function createTween() { 
  new Between(1, 10) 
    .time(4000) 
    .easing(Between.Easing.Cubic.InOut) 
    .on('update', v => { 
    value.innerText = `value: ${v.toFixed(2)}`; 
  }).on('start', () => { 
    state.innerText = 'state: running'; 
  }).on('complete', () => { 
    state.innerText = 'state: complete'; 
  }); 
} 

window.onload = () => setTimeout(createTween, 2000);


We used an easing-functions package and expose its functions through the Between. Easing object. So if you mind expanding the easing collection, please contribute to that package and we’ll update its version in the dependencies to include more varieties of easings. At the moment we support 31 easing modes:

    Easing — functions modes

                                                                              Easing — functions modes

Depending on your skills and experience easing API can be a powerful instrument you can try in your future projects, just, for example, an object transforms transition:

 Easing animation transition example.

                                        Easing animation transition example.


const element = document.querySelector('#rocket');
const state = document.querySelector('[data-state]');

function createTween() {
  const from = {x: 0, y: 0, r: 0};
  const to = {x: 300, y: -200, r: -40};
 
  new Between(from, to)
    .time(3000)
    .easing(Between.Easing.Cubic.InOut)
    .on('update', v => {
    element.style.transform =
      `translate(${v.x}px, ${v.y}px) rotate(${v.r}deg)`;
  }).on('complete', () => {
    layDown();
  });
}

function layDown() {
  const from = {x: 300, y: -200, r: -40};
  const to = {x: 350, y: -200, r: -80};
 
  new Between(from, to)
    .time(1000)
    .easing(Between.Easing.Quadratic.InOut)
    .on('update', v => {
    element.style.transform =
      `translate(${v.x}px, ${v.y}px) rotate(${v.r}deg)`;
  }).on('complete', () => {
    new Between(0, 100)
    .easing(Between.Easing.Cubic.InOut)
    .time(900).on('update', v => {
      element.style.filter = `grayscale(${v}%)`;
    });
  })
}

window.onload = () => 
setTimeout(createTween, 2000);

A few days ago a GitHub contributor added an another feature — “pausing tweens” pause(), play(), isPaused() methods. These can be used for dynamic interaction with tweens and expands between.js possibilities in usage by video games and creative websites. Below is a simple example of how it can be used:

const state = document.querySelector('#tweeningState');

document.querySelector('#playPauseButton')
  .addEventListener('click', toggle());

let between = new Between(0, 400)
  .time(2000)
  .on('pause', () => {
    state.innerText = 'state: paused';
  }).on('play', () => {
    state.innerText = 'state: running';
  });


function toggle() {
  if (between.isPaused)
    between.play();
  else  
    between.pause();
}

                                                       Pausing/resuming a tween 

const element = document.querySelector('#move'); 
const state = document.querySelector('[data-state]'); 
let between; 

function createTween() { 
  between = new Between(0, 400) 
    .time(2000) 
    .easing(Between.Easing.Cubic.InOut) 
    .on('update', v => { 
    element.style.transform = `translateX(${v}px)`; 
  }).on('start', () => { 
    state.innerText = 'state: running'; 
  }).on('pause', () => { 
    state.innerText = 'state: paused'; 
  }).on('play', () => { 
    state.innerText = 'state: running'; 
  }).on('complete', () => { 
    state.innerText = 'state: complete'; 
  }); 
} 

function toggle() { 
  if (!between) return; 
  if (between.isPaused) { 
    between.play(); 
  } else { 
    between.pause(); 
  } 
}


Color plugin

When Between.js was almost ready we decided to develop an additional color plugin, that provides a comfortable color change way.

Color types that are supported by the plugin:

  • RGB or RGBA
  • HEX
  • HSL
  • Keywords (red, green…)

  Node/Webpack color plugin usage example

                                                           Node/Webpack color plugin usage example

 Color plugin in action                                                        Color plugin in action


const value = document.querySelector('[data-value]'); 
const state = document.querySelector('[data-state]'); 

function createTween() { 
  new Between('green', 'rgb(255, 0, 0)') 
    .time(5000) 
    .easing(Between.Easing.Cubic.InOut) 
    .on('update', v => { 
    value.innerText = `Color: ${v}`; 
    value.style.color = v; 
  }).on('start', () => { 
    state.innerText = 'state: running'; 
  }).on('complete', () => { 
    state.innerText = 'state: complete'; 
  }); 
} 

window.onload = () => setTimeout(createTween, 2000);


What’s inside of a Color plugin?

We used the following dependencies:

1.color — A nice library that was used for color conversion inside of initialize() function

JavaScript library for immutable color conversion and manipulation with support for CSS color strings.

2.color-string — We used it to check if a string is a color keyword. Either is a dependency of a library above.

library for parsing and generating CSS color strings.

3.lerp — A neat linear interpolation function, written by Matt DesLauriers.

The implementation uses Plugins API and includes the following functions inside of a plugin object:

  • Testing if the startValue should be covered by the color plugin or not
test(startValue) { // rgb(255, 0, 0)
  return startValue.indexOf('rgb') >= 0
    || startValue.indexOf('#') >= 0
    || startValue.indexOf('hsl') >= 0
    || (typeof startValue === 'string' && colorString.get.rgb(startValue)); // true
}

  • Initializing the startValue and destValue. And it does an immediate conversion from color strings to arrays of format [r, g, b] that can be interpolated easily.
initialize(startValue, destValue) {
  return {
    data: {
      format: (startValue.indexOf('rgba') >= 0 && 'rgba')
       || (startValue.indexOf('rgb') >= 0 && 'rgb')
       || (startValue.indexOf('#') >= 0 && 'hex')
       || Color(startValue).model
    },
    startValue: Color(startValue).rgb(),
    destValue: Color(destValue).rgb()
  }
}


  • Color arrays interpolation. This step is executed inside of update()function of a tween object. The output is converted back to string and is stored in this.value
interpolate(startValue, destValue, progress, data) { 
  const r = lerp(startValue.color[0], destValue.color[0], progress); 
  const g = lerp(startValue.color[1], destValue.color[1], progress); 
  const b = lerp(startValue.color[2], destValue.color[2], progress); 
  const a = lerp(startValue.valpha, destValue.valpha, progress); 

  const color = Color.rgb(r, g, b, a)[data.format === 'rgba' ? 'rgb' : data.format](); 

  return typeof color === 'string' ? color : color.string(); 
}


Integration

So let’s consider we are all webpack users and interested in the dom-color plugin and we want to include it the usual way — with harmony modules. It’s a little bit more tricky than in a browser but turns out it is just as simple as it can be. The only thing we have to do is to import a ColorPlugin (as default) and assign it to internal Between._plugins object. The property name doesn’t matter, but I advise you to keep its name related to avoid further confusions.

 Color & easing integration

                                                         Color & easing integration 


Color & easing integration in action

                                                            Color & easing integration in action


More about integration can be found in the documentation on GitHub.


To sum up…

…it was an interesting experience for us to create something new, redesign old but gold patterns to follow modern ES6 trends and expand functionality with plugin support integration.

We really want you to try between.js in action and join our community to support open source software development, so please open a new issue and propose your ideas. We are open to bringing some more plugins to live soon!

Big thanks to Alexandr Kornienko for a collaboration, he helped me to prepare this article and developed some cool visual demos for easing.


Alexander

Alexander Buzin

Blog Author

Senior full-stack & VR/AR developer at AppReal-VR and Lead 3D developer at Vintage Web Production with 7+ years of experience in front-end development and 4+ years in Open source development

Leave a Reply

Your email address will not be published. Required fields are marked *

Top comments

Jessica

16 November 2018 at 4:19pm
Some good tips. I think the important points are on the topic

SUBSCRIBE OUR BLOG

Follow Us On

Share on

other Blogs

20% Discount