Animations
One of the ways to improve the attractiveness of your application is by adding animations. NativeScript exposes a simple and easy, but powerful enough API to allow animating almost every native element in your application.
For your convenience, we exposed two ways of creating animations
- Imperative (Animation
class from
ui/animation
module) and Declarative (CSS3
keyframe animations). The Imperative way provides full control
of any animation by calling animation methods directly via the
NativeScript ui/animation
module. The declarative
way uses the familiar CSS3 animations API and create CSS
keyframe animations. more information about CSS Animations vsm
be found in this section, while the current article will provide
detailed informatiion on how to use the NativeScript
Animation
module.
The following classes, interfaces and enums can be explicitly used in your applications.
// Main Animation class
const Animation = require("tns-core-modules/ui/animation").Animation;
// Main Animation class
// ALL animating properties at /api-reference/interfaces/_ui_animation_.animationdefinition
import {
Animation,
AnimationDefinition,
Pair // Pair: Defines a pair of values (horizontal and vertical) for translate and scale animations.
} from "tns-core-modules/ui/animation";
// AnimationCurveEnum: ease, easeIn, easeInOut, easeOut, linear, spring
const AnimationCurve = require("tns-core-modules/ui/enums").AnimationCurve;
// Full list of animating properties at /api-reference/interfaces/_ui_animation_.animationdefinition
const AnimationDefinition = require("tns-core-modules/ui/animation").AnimationDefinition;
// Defines a pair of values (horizontal and vertical) for translate and scale animations.
const Pair = require("tns-core-modules/ui/animation").Pair;
Animating Properties
NativeScript allows us to animate the properties of the element
we want. Once the parameters of the animate method are set (e.g.
scale
, rotate
, duration
,
etc.), the properties will be animated.
NativeScript lets you animate the following properties:
opacity
backgroundColor
translateX
and translateY
scaleX
and scaleY
rotate
width
and height
In every animation, you can control the following properties:
duration
: The length of the animation.
delay
: The amount of time to delay starting the
animation. iterations
: Specifies how many times the
animation should be played. curve
: The speed curve
of the animation. Available options are defined below.
Property values:
JavaScript Property | Value Description |
---|---|
backgroundColor |
Accepts hex or Color value.
|
curve |
Timing funciton that uses the
AnimationCurve enumeration.
|
delay |
Delay the animation start in milliseconds. |
duration |
Duration of animation in milliseconds. |
iterations |
Number of times to repeat the animation. |
opacity |
Number value (0 - 1 where 0 is full opacity). |
rotate |
Number value for degrees (0 - 360 degrees). |
scale |
Object value { x:1, y:2 } (1 = Original
scale).
|
translate |
Object value { x:100, y:200 } (Translate by
given DIPs).
|
width |
Number value. |
height |
Number value. |
The first example will change the background color of a
view
from "red" to "green".
view.backgroundColor = new Color("red");
view.animate({
backgroundColor: new Color("green"),
duration: 2000
});
view.backgroundColor = new Color("red");
view.animate({
backgroundColor: new Color("green"),
duration: 2000
});
The following example shows a test case where all the properties are used in single animation.
myView.animate({
backgroundColor: new Color("#414b7d"),
curve: AnimationCurve.easeOut,
delay: 300,
duration: 3000,
iterations: 3,
opacity: 0.8,
rotate: 360,
scale: {
x: 2,
y: 2
},
translate: {
x: 0,
y: 200
}
}).then(() => {
console.log("Animation finished");
}).catch((e) => {
console.log(e.message);
});
view.animate({
backgroundColor: new Color("#414b7d"),
curve: AnimationCurve.easeOut,
delay: 300,
duration: 3000,
iterations: 3,
opacity: 0.8,
rotate: 360,
scale: {
x: 2,
y: 2
},
translate: {
x: 0,
y: 200
}
}).then(() => {
console.log("Animation finished");
}).catch((e) => {
console.log(e.message);
});
By default, an animation moves with a linear speed without
acceleration or deceleration. This might look unnatural and
different from the real world where objects need time to reach
their top speed and can't stop immediately. The animation curve
(sometimes called an easing or timing function) is used to give
animations an illusion of inertia. It controls the animation
speed by modifying the fraction of the duration. NativeScript
comes with a number of predefined animation curves defined in
AnimationCurve
enumeration.
-
linear
: The simplest animation curve is linear. It maintains a constant speed while the animation is running. -
easeIn
: The ease-in curve causes the animation to begin slowly, and then speed up as it progresses. -
easeOut
: An ease-out curve causes the animation to begin quickly, and then slow down as it completes. -
easeInOut
: An ease-in ease-out curve causes the animation to begin slowly, accelerate through the middle of its duration, and then slow again before completing. -
spring
: A spring animation curve causes an animation to produce a spring (bounce) effect.
In NativeScript, the animation curve is represented by the
AnimationCurve
enumeration and can be specified
with the curve property of the animation.
view.animate({
translate: {
x: 0,
y: 100
},
duration: 1000,
curve: AnimationCurve.easeIn
});
view.animate({
translate: {
x: 0,
y: 100
},
duration: 1000,
curve: AnimationCurve.easeIn
});
Experiment with the different animation timing functions in the NativeScript Playground
It is easy to create your own animation curve by passing in the X and Y components of two control points of a cubic Bezier curve. Using Bezier curves is a common technique to create smooth curves in computer graphics and they are widely used in vector-based drawing tools. The values passed to the cubicBezier method control the curve shape. The animation speed will be adjusted based on the resulting path.
For help finding the cubicBezier values you need for your custom animation timing function, use the visual tools on cubic-bezier.com. Once you find an animation path you like, simply copy and paste the cubic bezier values and paste them in the AnimationCurve.cubicBezier function. There should be four numbers (X,Y coordinates for each of the two points in the animation).
view.animate({
translate: {
x: 0,
y: 100
},
duration: 1000,
curve: AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
view.animate({
translate: {
x: 0,
y: 100
},
duration: 1000,
curve: AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
To animate a target view (or to create a complex animation for
multiple views/layouts) we can an array of
AnimationDefinition
and pass it to the
Animation
constructor. Using the
target
proerty in the definition, allows us full
control of the animation object via code.
const myView = args.object;
const animationDefinition = {
target: myView, // provide the view to animate
curve: AnimationCurve.easeOut,
duration: 1000,
scale: {
x: 0.2,
y: 0.2
},
translate: {
x: -50,
y: -50
}
};
animation = new Animation([animationDefinition], false);
animation.play()
.then(() => {
console.log("Animation finished");
console.log("animation.isPlaying: ", animation.isPlaying);
}).catch((e) => {
console.log(e.message);
});
const view = args.object;
const animationDefinition: AnimationDefinition = {
target: view, // provide the view to animate
curve: AnimationCurve.easeOut,
duration: 1000,
scale: {
x: 0.2,
y: 0.2
},
translate: {
x: -50,
y: -50
}
};
animation = new Animation([animationDefinition], false);
animation.play()
.then(() => {
console.log("Animation finished");
console.log("animation.isPlaying: ", animation.isPlaying);
}).catch((e) => {
console.log(e.message);
});
Cancelling an animation via the cancel
method.
animation.cancel();
animation.cancel();
Chaining Animations
Chained animations allows us to create a chain of conseclutive
animations for one or multiple views. The
animate
method returns a promise that we can use to
chain multiple animations.
const duration = 300;
myView.animate({
opacity: 0,
duration: duration
}).then(() => myView.animate({
opacity: 1,
duration: duration
})).then(() => myView.animate({
translate: {
x: 200,
y: 200
},
duration: duration
})).then(() => myView.animate({
translate: {
x: 0,
y: 0
},
duration: duration
})).then(() => myView.animate({
scale: {
x: 5,
y: 5
},
duration: duration
})).then(() => myView.animate({
scale: {
x: 1,
y: 1
},
duration: duration
}))
.then(() => myView.animate({
rotate: 180,
duration: duration
}))
.then(() => myView.animate({
rotate: 0,
duration: duration
})).then(() => {
console.log("Animation finished");
}).catch((e) => {
console.log(e.message);
});
const duration = 300;
myView.animate({
opacity: 0,
duration: duration
}).then(() =>
myView.animate({
opacity: 1,
duration: duration
})
).then(() =>
myView.animate({
translate: {
x: 200,
y: 200
},
duration: duration
})
).then(() =>
myView.animate({
translate: {
x: 0,
y: 0
},
duration: duration
})
).then(() =>
myView.animate({
scale: {
x: 5,
y: 5
},
duration: duration
})
).then(() =>
myView.animate({
scale: {
x: 1,
y: 1
},
duration: duration
})
).then(() =>
myView.animate({
rotate: 180,
duration: duration
})
).then(() =>
myView.animate({
rotate: 0,
duration: duration
})
).then(() => {
console.log("Animation finished");
}).catch(e => {
console.log(e.message);
});
Multiple Views Animation
When needed, we can animate multiple views simultaneously. It is
as easy as placing all the animations in a single array and
playing them with the help of Animation
class.
const definitions = [];
const definition1 = {
target: view1,
translate: {
x: 200,
y: 0
},
duration: 1000
};
definitions.push(definition1);
const definition2 = {
target: view2,
translate: {
x: 0,
y: 200
},
duration: 1000
};
definitions.push(definition2);
const definition3 = {
target: view3,
translate: {
x: -200,
y: 0
},
duration: 1000
};
definitions.push(definition3);
const definition4 = {
target: view4,
translate: {
x: 0,
y: -200
},
duration: 1000
};
definitions.push(definition4);
const animationSet = new Animation(definitions);
animationSet.play()
.then(() => {
console.log("Animation finished");
}).catch((e) => {
console.log(e.message);
});
const definitions: Array<AnimationDefinition> = [];
const definition1: AnimationDefinition = {
target: view1,
translate: {
x: 200,
y: 0
},
duration: 1000
};
definitions.push(definition1);
const definition2: AnimationDefinition = {
target: view2,
translate: {
x: 0,
y: 200
},
duration: 1000
};
definitions.push(definition2);
const definition3: AnimationDefinition = {
target: view3,
translate: {
x: -200,
y: 0
},
duration: 1000
};
definitions.push(definition3);
const definition4: AnimationDefinition = {
target: view4,
translate: {
x: 0,
y: -200
},
duration: 1000
};
definitions.push(definition4);
const animationSet = new Animation(definitions);
animationSet.play()
.then(() => {
console.log("Animation finished");
}).catch((e) => {
console.log(e.message);
});
Origin Properties
To obtain more control over our animations, we can use
originX
and originY
proeprties.
-
originX
gets or sets the X component of the origin point around which the view will be transformed. The default value is 0.5 representing the centre of the view. -
originY
gets or sets the Y component of the origin point around which the view will be transformed. The default value is 0.5 representing the centre of the view.
By default, the value of 0.5 (for both X and Y coordinate) is
placing the ideal centre of our view. To create complex rotation
animations, we can change the origin properties. In the example
below, we are demonstrating how to rotate a view against
different originX
and originY
points.
function animate() {
// myFirstView.originX = 0.5; // Default value (center of the view's horizontal axis)
// myFirstView.originY = 0.5; // Default value (middle of the view's vertical axis)
myFirstView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myFirstView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
mySecondView.originX = 1; // most right of horizontal axis
mySecondView.animate({
rotate: 360,
duration: 3000
}).then(() => {
mySecondView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myThirdView.originX = 0; // most left of horizontal axis
myThirdView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myThirdView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myForthView.originY = 1; // bottom of vertical axis
myForthView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myForthView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myFifthView.originY = 0; // top of vertical axis
myFifthView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myFifthView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
}
exports.animate = animate;
export function animate() {
// myFirstView.originX = 0.5; // Default value (center of the view's horizontal axis)
// myFirstView.originY = 0.5; // Default value (middle of the view's vertical axis)
myFirstView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myFirstView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
mySecondView.originX = 1; // most right of horizontal axis
mySecondView.animate({
rotate: 360,
duration: 3000
}).then(() => {
mySecondView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myThirdView.originX = 0; // most left of horizontal axis
myThirdView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myThirdView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myForthView.originY = 1; // bottom of vertical axis
myForthView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myForthView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
myFifthView.originY = 0; // top of vertical axis
myFifthView.animate({
rotate: 360,
duration: 3000
}).then(() => {
myFifthView.rotate = 0;
}).catch((e) => {
console.log(e.message);
});
}
<GridLayout rows="auto, *, *, *, *" columns="*, *" backgroundColor="white">
<Button row="0" col="0" colSpan="2" text="Animate" tap="animate" class="btn btn-primary btn-active" />
<GridLayout row="1" col="0" colSpan="2" id="myFirstView" automationText="myFirstView" width="400" height="40" >
<Label text="Default originX / originY (0.5)" horizontalAlignment="center"/>
</GridLayout>
<GridLayout row="2" col="0" colSpan="2" id="mySecondView" automationText="mySecondView" width="400" height="40" >
<Label text="originX = 1" horizontalAlignment="right"/>
</GridLayout>
<GridLayout row="3" col="0" colSpan="2" id="myThirdView" automationText="myThirdView" width="400" height="40" >
<Label text="originX = 0" horizontalAlignment="left"/>
</GridLayout>
<GridLayout row="4" col="0" id="myForthView" automationText="myForthView" width="100" height="300" >
<Label text="originY = 1" verticalAlignment="bottom"/>
</GridLayout>
<GridLayout row="4" col="1" id="myFifthView" automationText="myFifthView" width="100" height="300" >
<Label text="originY = 0" verticalAlignment="top"/>
</GridLayout>
</GridLayout>
Width Height Properties
Since {N} 6.0, we can animate the width
and
height
properties of views. On the snippets below
are demonstrated, how to configure those animations:
const AnimationCurve = require("tns-core-modules/ui/enums").AnimationCurve;
function animateWidth(args) {
const button = args.object;
const page = button.page;
const myView = page.getViewById("lbl");
myView.animate({
width:320,
duration: 1000,
curve: AnimationCurve.easeIn
});
}
exports.animateWidth = animateWidth;
function animateHeight(args) {
const button = args.object;
const page = button.page;
const myView = page.getViewById("lbl");
myView.animate({
height:400,
duration: 1000,
curve: AnimationCurve.easeIn
});
}
exports.animateHeight = animateHeight;
import { AnimationCurve } from "tns-core-modules/ui/enums";
import { View } from "tns-core-modules/ui/core/view";
export function animateWidth(args) {
let button = args.object;
let page = button.page;
let view = <View>page.getViewById("lbl");
view.animate({
width: 320,
duration: 1000,
curve: AnimationCurve.easeIn
});
}
export function animateHeight(args) {
let button = args.object;
let page = button.page;
let view = <View>page.getViewById("lbl");
view.animate({
height: 400,
duration: 1000,
curve: AnimationCurve.easeIn
});
}
<GridLayout rows="auto, auto, *">
<Button row="0" text="Animate height" tap="animateHeight" class="btn btn-primary btn-active" width="80%"/>
<Button row="1" text="Animate width" tap="animateWidth" class="btn btn-primary btn-active" width="80%"/>
<Label row="2" id="lbl" text="NativeScript" textWrap="true" marginTop="50"/>
</GridLayout>
API Reference for the Animation Class
API Reference for the CubicBezierAnimationCurve Class
API Reference for the AnimationDefinition Interface
API Reference for the Cancelable Interface
API Reference for the Pair Interface
API Reference for the AnimationCurve Enum
See also: CSS Animations