KUTE.js is a very flexible animation engine that allows you to extend beyond measure. In this tutorial we'll dig into what's best to do to extend/customize the functionality of KUTE.js core, plugins and features.
The best way to extend, no matter what you would like to achieve is to use a specific closure, here's an example:
/* KUTE.js - The Light Tweening Engine
* by dnp_theme
* package - pluginName
* desc - what your plugin does
* pluginName by yourNickname aka YOUR NAME
* Licensed under MIT-License
*/
(function (root,factory) {
if (typeof define === 'function' && define.amd) {
define(['kute.js'], factory);
} else if(typeof module == 'object' && typeof require == 'function') {
module.exports = factory(require('kute.js'));
} else if ( typeof root.KUTE !== 'undefined' ) {
factory(root.KUTE);
} else {
throw new Error("pluginName require KUTE.js.");
}
}(this, function (KUTE) {
// your code goes here
// in this function body
// the plugin returns this
return this;
}));
As suggested in the above template, your function body could be written with one or more of the examples below.
In some cases, you may want to extend with additional tween control methods, so before you do that, make sure to check Tween
function to get the internal references notation:
//add a reference to _tween function
var Tween = KUTE.Tween;
// let's add a timescale method
Tween.prototype.timescale = function(factor){
this.options.duration *= factor;
return this;
}
// or let's add a reverse method
Tween.prototype.reverse = function(){
for (var p in this.valuesEnd) {
var tmp = this.valuesRepeat[p]; // we cache this object first
this.valuesRepeat[p] = this.valuesEnd[p];
this.valuesEnd[p] = tmp;
this.valuesStart[p] = this.valuesRepeat[p];
}
return this;
}
// go back in time
Tween.prototype.seek = function (time) {
this._startTime -= time;
return this;
};
// how about a restart method
Tween.prototype.restart = function(){
if (this.playing) {
this.stop();
this.start();
}
return this;
}
// methods to queue callbacks with ease
Tween.prototype.onUpdate = function(){
this.options.update = arguments;
return this;
}
For some reasons these methods aren't included into the core/plugins by default, but let you decide what you need and how to customize the animation engine for your very specific need.
KUTE.js core engine and plugins cover what I consider to be most essential for animation, but you may have a different opinion. In case you may want to know how to animate properties that are not currently supported, stick to this guide and you'll master it real quick, it's very easy.
We need basically 3 functions:
KUTE.prepareStart['propertyName']
required a function to get the current value of the property required for the .to()
method;KUTE.parseProperty['propertyName']
required a function to process the user value / current value to have it ready to tween;KUTE.dom['propertyName']
required a domUpdate function that will update the property value into the DOM;KUTE.crossCheck['propertyName']
optional a function to help you set proper values when for instance startValues unit is different than endValues unit; so far this is used for CSS3/SVG transforms
and SVG Morph, but it can be extended for many properties such as box-model properties or border-radius properties;So let's add support for boxShadow! It should be a medium difficulty guide most developers can follow and the purpose of this guide is to showcase how easy it actually is to extend KUTE.js. So grab the above template and let's break it down to pieces:
// add a reference to global and KUTE object
var g = typeof global !== 'undefined' ? global : window, K = KUTE,
// add a reference to KUTE utilities
prepareStart = K.prepareStart, getCurrentStyle = K.getCurrentStyle,
property = K.property, parseProperty = K.parseProperty, trueColor = K.truC,
DOM = K.dom, color = g.Interpolate.color, unit = g.Interpolate.unit; // interpolation functions
// the preffixed boxShadow property, mostly for legacy browsers
// maybe the browser is supporting the property with its vendor preffix
// box-shadow: none|h-shadow v-shadow blur spread color |inset|initial|inherit;
var _boxShadow = property('boxShadow'); // note we're using the KUTE.property() autopreffix utility
var colRegEx = /(\s?(?:#(?:[\da-f]{3}){1,2}|rgba?\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\))\s?)/gi; // a full RegEx for color strings
// for browsers that don't support the property, use a filter
// if (!(_boxShadow in document.body.style)) {return;}
Now we have access to the KUTE object, prototypes and it's utility functions, let's write a prepareStart
function that will read the current boxShadow
value:
// for the .to() method, you need to prepareStart the boxShadow property
// which means you need to read the current computed value
// if the current computed value is not acceptable, use a default value
prepareStart['boxShadow'] = function(property,value){
var cssBoxShadow = getCurrentStyle(this.element,'boxShadow'); // where getCurrentStyle() is an accurate method to read the current property value
return /^none$|^initial$|^inherit$|^inset$/.test(cssBoxShadow) ? '0px 0px 0px 0px rgb(0,0,0)' : cssBoxShadow; // if the current value is not valid, use a default one
}
// note that in some cases the window.getComputedStyle(this.element,null) can be faster or more appropriate
// we are using a hybrid function that's trying to get proper colors and other stuff
// some legacy browsers lack in matters of accuracy so the KUTE.js core methods would suffice
// also to read the current value of an attribute, replace first line of the above function body with this
// var attrValue = element.getAttribute(property);
// and return the value or a default value, mostly rgb(0,0,0) for colors, 1 for opacity, or 0 for most other types
Since KUTE.js 1.6.0, the tween object is bound to all property parsing utilities, meaning that you have access to this
that has the target element, options, start and end values and everything else. This is extremely useful if
you want to optimize and/or extend particular properties values, the dom update functions, and even override the property name
Now we need an utility function that makes sure the structure looks right for the DOM update function.
// utility function to process values accordingly
// numbers must be floats/integers and color must be rgb object
var processBoxShadowArray = function(shadow){
var newShadow;
// properly process the shadow based on amount of values
if (shadow.length === 3) { // [h-shadow, v-shadow, color]
newShadow = [shadow[0], shadow[1], 0, 0, shadow[2], 'none'];
} else if (shadow.length === 4) { // [h-shadow, v-shadow, color, inset] | [h-shadow, v-shadow, blur, color]
newShadow = /inset|none/.test(shadow[3]) ? [shadow[0], shadow[1], 0, 0, shadow[2], shadow[3]] : [shadow[0], shadow[1], shadow[2], 0, shadow[3], 'none'];
} else if (shadow.length === 5) { // [h-shadow, v-shadow, blur, color, inset] | [h-shadow, v-shadow, blur, spread, color]
newShadow = /inset|none/.test(shadow[4]) ? [shadow[0], shadow[1], shadow[2], 0, shadow[3], shadow[4]] : [shadow[0], shadow[1], shadow[2], shadow[3], shadow[4], 'none'];
} else if (shadow.length === 6) { // ideal [h-shadow, v-shadow, blur, spread, color, inset]
newShadow = shadow;
}
// make sure the numbers are ready to tween
for (var i=0; i<4; i++){
newShadow[i] = parseFloat(newShadow[i]);
}
// make sure color is an rgb object
newShadow[4] = trueColor(newShadow[4]); // where K.truC()/trueColor is a core method to return the true color in rgb object format
return newShadow;
};
Next we'll need to write a parseProperty
function that will prepare the property value and build an Object or Array of values ready to tween. This function also registers the KUTE.dom['boxShadow']
function into the
KUTE object, and this way we avoid filling the main object with unnecessary functions, just to keep performance tight.
// the parseProperty for boxShadow
// registers the window.dom['boxShadow'] function
// returns an array of 6 values in the following format
// [horizontal, vertical, blur, spread, color: {r:0,g:0,b:0}, inset]
parseProperty['boxShadow'] = function(property,value,element){
// the DOM update function for boxShadow registers here
// we only enqueue it if the boxShadow property is used to tween
if ( !('boxShadow' in DOM) ) {
DOM['boxShadow'] = function(element,property,startValue,endValue,progress) { // element, propertyName, valuesStart.boxShadow, valuesEnd.boxShadow, progress
// let's start with the numbers | set unit | also determine inset
var numbers = [], px = 'px', // the unit is always px
inset = startValue[5] !== 'none' || endValue[5] !== 'none' ? ' inset' : false;
for (var i=0; i<4; i++){ // for boxShadow coordinates we do the math for an array of numbers
numbers.push( unit(startValue[i], endValue[i], px, progress) );
}
// now we handle the color
var colorValue = color(startValue[4],endValue[4],progress);
// last piece of the puzzle, the DOM update
element.style[_boxShadow] = inset ? colorValue + numbers.join(' ') + inset : colorValue + numbers.join(' ');
};
}
// processProperty for boxShadow, builds basic structure with ready to tween values
if (typeof value === 'string'){
var shadowColor, inset = 'none';
// make sure to always have the inset last if possible
inset = /inset/.test(value) ? 'inset' : inset;
value = /inset/.test(value) ? value.replace(/(\s+inset|inset+\s)/g,'') : value;
// also getComputedStyle often returns color first "rgb(0, 0, 0) 15px 15px 6px 0px inset"
shadowColor = value.match(colRegEx);
value = value.replace(shadowColor[0],'').split(' ').concat([shadowColor[0].replace(/\s/g,'')],[inset]);
// now we can use the above specific utitlity
value = processBoxShadowArray(value);
} else if (value instanceof Array){
value = processBoxShadowArray(value);
}
return value;
}
And now, we are ready to tween both .to()
and .fromTo()
methods:
// tween to a string value
var myBSTween1 = KUTE.to('selector', {boxShadow: '15px 25px #069'}).start();
// or a fromTo with string and array, hex and rgb
var myBSTween2 = KUTE.fromTo('selector', {boxShadow: [15, 25, 0, '#069']}, {boxShadow: '0px 0px rgb(0,0,0)'}).start();
// maybe you want to animate an inset boxShadow?
var myBSTween3 = KUTE.fromTo('selector', {boxShadow: [5, 5, 0, '#069', 'inset']}, {boxShadow: '0px 0px rgb(0,0,0)'}).start();
You are now ready to demo!
This plugin should be compatible with IE9+ and anything that supports boxShadow
CSS property. As you can see it can handle pretty much anything you throw at it, but it requires at least 3 values: h-shadow, v-shadow, and color
because Safari doesn't work without a color. Also this plugin won't be able to handle multiple instances of boxShadow
for same element, because the lack of support on legacy browsers, also the color cannot be RGBA, but hey,
it supports both outline and inset shadows and you can fork it anyway to your liking.
If you liked this tutorial, feel free to write your own, a great idea would be for textShadow
, it's very similar to the above example plugin.
getElementById
or querySelector
when multi
argument is null or false, BUT
when true, querySelectorAll
is used and returns a HTMLCollection object.if (/undefined/.test(KUTE.property('propertyName')) ) { /* legacy browsers */ } else { /* modern browsers */ }
getComputedStyle
function to get the current value of the property required for the .to()
method; it actually checks in element.style
,
element.currentStyle
and window.getComputedStyle(element,null)
to make sure it won't miss the property value;{v: 150, u: 'px'}
object for any box model or a single numeric value based property and make it ready to tween. When a second
parameter is set to true it will return an object with value and unit specific for rotation angles and skews.{r: 150, g: 150, b: 0}
color object ready to tween; if the color value is a web safe color,
the IE9+ browsers will be able to return the rgb object we need.{r: 150, g: 150, b: 0}
color object;#006699
color string.width: 250px