Develop a Windows 8 app in 30 days
A good-looking application must provide user with visual feedback. Users must always know that an order (a click, a tap or whatever) is well received and understood by the application and animations are a great tool to do so.
The new HTML 5 specification (to be honest, I should say "the new CSS 3 specification") introduces a great tool to handle simple animations: the transitions.
According to "CSS Transitions Module Level 3" specification on W3C site, CSS3 Transitions allows property changes in CSS values to occur smoothly over a specified duration.
The aim of this article will be to first describe the concept of transitions and then to see how CSS3 Transitions works and how we can handle browsers that don’t support the feature:
- CSS3 Transitions
- Putting it all together
- Transitions without CSS3 Transitions
- Conclusion
- Going further
In addition, I suggest you to read the "Introduction to CSS3 Animations" (by David Rousset) which is an excellent companion for this article.
To see how CSS3 Transitions can be used, I developed is a sample of a game which uses CSS3 Transitions to animate cells of a puzzle (and which will fallback to JavaScript if your browser doesn’t support CSS3 Transitions):
CSS3 Transitions
Introduction
At the beginning, the W3C CSS workgroup resisted adding transitions to CSS arguing that transitions are not really style properties. But eventually designers and developers managed to convince them that transitions is about dynamic styles and can take place in a CSS file.
According to the W3C site, CSS3 Transitions are able to animate the following types of properties: (click here to show them)
- color: interpolated via red, green, blue and alpha components (treating each as a number, see below)
- length: interpolated as real numbers.
- percentage: interpolated as real numbers.
- integer: interpolated via discrete steps (whole numbers). The interpolation happens in real number space and is converted to an integer using floor().
- number: interpolated as real (floating point) numbers.
- transform list: see CSS Transforms specification: http://www.w3.org/TR/css3-2d-transforms/
- rectangle: interpolated via the x, y, width and height components (treating each as a number).
- visibility: interpolated via a discrete step. The interpolation happens in real number space between 0 and 1, where 1 is "visible" and all other values are "hidden".
- shadow: interpolated via the color, x, y and blur components (treating them as color and numbers where appropriate). In the case where there are lists of shadows, the shorter list is padded at the end with shadows whose color is transparent and all lengths (x, y, blur) are 0.
- gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated.
- paint server (SVG): interpolation is only supported between: gradient to gradient and color to color. They then work as above.
- space-separated list of above: If the lists have the same number of items, each item in the list is interpolated using the rules above. Otherwise, no interpolation.
- a shorthand property: If all the parts of a shorthand can be animated, then interpolation is performed as if each property was individually specified.
And the following properties must be supported for transitions:(click here to show them)
- background-color (color)
- background-image (only gradients)
- background-position (percentage and length)
- border-bottom-color (color)
- border-bottom-width (length)
- border-color (color)
- border-left-color (color)
- border-left-width (length)
- border-right-color (color)
- border-right-width (length)
- border-spacing (length)
- border-top-color (color)
- border-top-width (length)
- border-width (length)
- bottom (length and percentage)
- color (color)
- crop (rectangle)
- font-size (length and percentage)
- font-weight (number)
- grid-* (various)
- height (length and percentage)
- left (length and percentage)
- letter-spacing (length)
- line-height (number, length and percentage)
- margin-bottom (length)
- margin-left (length)
- margin-right (length)
- margin-top (length)
- max-height (length and percentage)
- max-width (length and percentage)
- min-height (length and percentage)
- min-width (length and percentage)
- opacity (number)
- outline-color (color)
- outline-offset (integer)
- outline-width (length)
- padding-bottom (length)
- padding-left (length)
- padding-right (length)
- padding-top (length)
- right (length and percentage)
- text-indent (length and percentage)
- text-shadow (shadow)
- top (length and percentage)
- vertical-align (keywords, length and percentage)
- visibility (visibility)
- width (length and percentage)
- word-spacing (length and percentage)
- z-index (integer)
- zoom (number)
SVG
The properties of SVG objects are animatable when they are defined as animatable:true in the SVG specification: http://www.w3.org/TR/SVG/struct.html.
Declarations
To declare a transition in a CSS file, you just have to write the following code:
69.transition-property: all;
70.transition-duration: 0.5s;
71.transition-timing-function: ease;
72.transition-delay: 0s;
This declaration defines that any update on any property will be done in 0.5s (and not immediately so).
You can also define your translations on a per property basis:
73.transition-property: opacity left top;
74.transition-duration: 0.5s 0.8s 0.1s;
75.transition-timing-function: ease linear ease;
76.transition-delay: 0s 0s 1s;
And finally you can use the shorthand property "transition" to define all you need in a single line:
77.transition: all 0.5s ease 0s;
In this shorthand version you can precise as many properties as you want separated by a comma:
78.transition: opacity 0.5s ease 0s, left 0.8s linear 0s;
The transitions will be triggered when a property of the target object is updated. The update can be done with JavaScript or using CSS3 by assign new class to a tag.
For example, using IE10 if you have the following CSS3 declaration:
79.-ms-transition-property: opacity left top;
80.-ms-transition-duration: 0.5s 0.8s 0.5s;
81.-ms-transition-timing-function: ease linear ease;
When you update the opacity of your tag, the current value will be animated to the new value over 0.5s with a ease timing function (which give a smooth animation).
Non Linear Transitions
The "transition-timing-function" line defines that the transition will not be linear but will use a timing function to produce a non linear animation.
Basically, CSS3 transitions will use cubic bezier curve to smooth the transition by computing different speed over its duration.
The following functions are supported:
- linear: Constant speed
- cubic-bezier: Speed will be computed according to a cubic bezier curve define by two control points: P0 et P1 (so you will have to define 4 values here: P0x,P0y and P1x, P1y.
- ease: Speed will be computed with cubic-bezier(0.25, 0.1, 0.25, 1)
- ease-in: Speed will be computed with cubic-bezier(0.42, 0, 1, 1)
- ease-inout: Speed will be computed with cubic-bezier(0.42, 0, 0.58, 1)
- ease-out: Speed will be computed with cubic-bezier(0, 0, 0.58, 1)
Here is a simulation tool (using SVG of course) to show the impact of each timing function:
<p>Your browser does not support iframes.</p> Click here to show the demo : <a href="http://www.catuhe.com/msdn/transitions/easingfunctions.htm">http://www.catuhe.com/msdn/transitions/easingfunctions.htm</a>
This simulator is written with pure JavaScript code to facilitate the understanding of the function:
88.TRANSITIONSHELPER.computeCubicBezierCurveInterpolation = function (t, x1, y1, x2, y2) {
89.
90.var f0 = 1 - 3 * x2 + 3 * x1;
91.var f1 = 3 * x2 - 6 * x1;
92.var f2 = 3 * x1;
93.
94.var refinedT = t;
95.for (var i = 0; i < 5; i++) {
96.var refinedT2 = refinedT * refinedT;
97.var refinedT3 = refinedT2 * refinedT;
98.
99.var x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
100. var slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
101. refinedT -= (x - t) * slope;
102. refinedT = Math.min(1, Math.max(0, refinedT));
103. }
104.
105.
106. return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 +
107. 3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 +
108. Math.pow(refinedT, 3);
109. };
This code is the implementation of the cubic bezier based on this definition and you can find the source of the simulator here.
Delay
The "transition-delay" line defines the delay between an update of a property and the start of the transition
Events
An event is raised at the end of a transition: "TransitionEnd". According to your browser the correct name will be:
- Chrome & Safari: webkitTransitionEnd
- Firefox: mozTransitionEnd
- Opera: oTransitionEnd
- Internet Explorer: MSTransitionEnd
The event will give you the following information:
- propertyName: Name of the animated property
- elapsedTime: The amount of time the transition has been running, in seconds
Here is an usage sample for IE10: 116. block.addEventListener("MSTransitionEnd", onTransitionEvent);
More about CSS3 transitions
I can mainly propose two reasons why CSS3 transitions are really useful:
- Hardware acceleration: CSS3 Transitions are directly handled on the GPU (where available) and produce smoother results. And it is really important on mobile devices where computing power is really limited
- Better separation between code and design: For me, the developer must not be aware of animations or anything related to design. In the same way the designer/artist must not be aware of JavaScript. That’s why CSS3 Transitions are really interesting as designers can describe all the transitions in the CSS without needing developers
Support and fallback
Since PP3, IE10 (which you can download with Windows "8" Developer Preview here) supports CSS3 Transitions:
This report was produced by http://caniuse.com/#search=CSS3 transitions.
Of course, as the specification is not finished (working draft), you must use vendor’s prefixes such as –ms-, –moz-, –webkit-, –o-.
We can obviously see that we need to provide a transparent solution in order to address all kind of browsers. The best way will be to develop an API that can detect the support of CSS3 transitions. If the browser doesn’t support the feature, we will fallback to some JavaScript code.
It is important to support a fallback method if you rely on transitions for websites functionalities. If you don’t want to do that, you should consider using transitions only for design enhancements. In this case, the site will still work but only supported browsers will deliver the full experience. We speak here of "progressive enhancements" as the more powerfull the browser is, the more features he gets.
Transitions without CSS3 Transitions
So to be able to support a fallback to CSS3 Transitions, we will develop a small toolkit to provide transitions by code.
First of all, we will create a container object for our namespace:
119. var TRANSITIONSHELPER = TRANSITIONSHELPER || {};
120.
121. TRANSITIONSHELPER.tickIntervalID = 0;
122.
123. TRANSITIONSHELPER.easingFunctions = {
124. linear:0,
125. ease:1,
126. easein:2,
127. easeout:3,
128. easeinout:4,
129. custom:5
130. };
131.
132. TRANSITIONSHELPER.currentTransitions = [];
To support the same level of easing functions, we must declare an "enum" with all required fields.
The toolkit is based on a function which is called every 17ms (to achieve animations at 60 fps). The function will enumerate through a collection of active transitions. For each transition the code will evaluate the next value given the current value and the target value.
We will need some handy functions to extract value of properties and units used:
133. TRANSITIONSHELPER.extractValue = function (string) {
134. try {
135. var result = parseFloat(string);
136.
137. if (isNaN(result)) {
138. return 0;
139. }
140.
141. return result;
142. } catch (e) {
143. return 0;
144. }
145. };
146.
147. TRANSITIONSHELPER.extractUnit = function (string) {
148.
149.
150. if (string == "") {
151. return "px";
152. }
153.
154. var value = TRANSITIONSHELPER.extractValue(string);
155. var unit = string.replace(value, "");
156.
157. return unit;
158. };
The main function will process active transitions and will call the cubic bezier function to evaluate current values:
159. TRANSITIONSHELPER.tick = function () {
160.
161. for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
162. var transition = TRANSITIONSHELPER.currentTransitions[index];
163.
164.
165. var currentDate = (new Date).getTime();
166. var diff = currentDate - transition.startDate;
167.
168. var step = diff / transition.duration;
169. var offset = 1;
170.
171.
172. switch (transition.ease) {
173. case TRANSITIONSHELPER.easingFunctions.linear:
174. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 1.0, 1.0);
175. break;
176. case TRANSITIONSHELPER.easingFunctions.ease:
177. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.25, 0.1, 0.25, 1.0);
178. break;
179. case TRANSITIONSHELPER.easingFunctions.easein:
180. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 1.0, 1.0);
181. break;
182. case TRANSITIONSHELPER.easingFunctions.easeout:
183. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 0.58, 1.0);
184. break;
185. case TRANSITIONSHELPER.easingFunctions.easeinout:
186. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step,0.42, 0, 0.58, 1.0);
187. break;
188. case TRANSITIONSHELPER.easingFunctions.custom:
189. offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, transition.customEaseP1X, transition.customEaseP1Y, transition.customEaseP2X, transition.customEaseP2Y);
190. break;
191. }
192.
193. offset *= (transition.finalValue - transition.originalValue);
194.
195. var unit = TRANSITIONSHELPER.extractUnit(transition.target.style[transition.property]);
196. var currentValue = transition.originalValue + offset;
197.
198. transition.currentDate = currentDate;
199.
200.
201. if (currentDate >= transition.startDate + transition.duration) {
202. currentValue = transition.finalValue;
203. TRANSITIONSHELPER.currentTransitions.splice(index, 1);
204. index--;
205.
206.
207. if (transition.onCompletion) {
208. transition.onCompletion({propertyName:transition.property,elapsedTime:transition.duration});
209. }
210. }
211.
212.
213. transition.target.style[transition.property] = currentValue + unit;
214. }
215. };
The current version of the toolkit only supports numeric values but if you want to animate complex values (such as color) you just have to decompose them to simple values.
Registering a transition in the system will be done using the following code:
216. TRANSITIONSHELPER.transition = function (target, property, newValue, duration, ease, customEaseP1X, customEaseP1Y,
customEaseP2X, customEaseP2Y, onCompletion) {
217.
218.
219. var transition = {
220. target: target,
221. property: property,
222. finalValue: newValue,
223. originalValue: TRANSITIONSHELPER.extractValue(target.style[property]),
224. duration: duration,
225. startDate: (new Date).getTime(),
226. currentDate: (new Date).getTime(),
227. ease:ease,
228. customEaseP1X:customEaseP1X,
229. customEaseP2X:customEaseP2X,
230. customEaseP1Y: customEaseP1Y,
231. customEaseP2Y: customEaseP2Y,
232. onCompletion: onCompletion
233. };
234.
235.
236. if (TRANSITIONSHELPER.tickIntervalID == 0) {
237. TRANSITIONSHELPER.tickIntervalID = setInterval(TRANSITIONSHELPER.tick, 17);
238. }
239.
240.
241. for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
242. var temp = TRANSITIONSHELPER.currentTransitions[index];
243.
244. if (temp.target === transition.target && temp.property === transition.property) {
245. TRANSITIONSHELPER.currentTransitions.splice(index, 1);
246. index--;
247. }
248. }
249.
250.
251. if (transition.originalValue != transition.finalValue) {
252. TRANSITIONSHELPER.currentTransitions.push(transition);
253. }
254. };
The "tick
" function is launched when the first transition is activated.
Finally you just have to use modernizr to define if CSS3 Transitions is supported by the current browser. If not, you can fallback to our toolkit.
The code for the TransitionsHelper can be downloaded here: http://www.catuhe.com/msdn/transitions/transitionshelper.js
For example, in my puzzle game, the following code is used to animate the cells:
255. if (!PUZZLE.isTransitionsSupported) {
256. TRANSITIONSHELPER.transition(block.div, "top", block.x * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
257. TRANSITIONSHELPER.transition(block.div, "left", block.y * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
258. }
259. else {
260. block.div.style.top = (block.x * totalSize + offset) + "px";
261. block.div.style.left = (block.y * totalSize + offset) + "px";
262. }
We can note that I could use another way to animate my cells when CSS3 transitions are supported: I could have defined a collection of CSS3 classes with predefined left and top values (one for each cell) to affect them to right cells.
Some frameworks and toolkits already exist to support software transitions:
By the way, you can also use the old good animate()
method of jQuery.
Conclusion
As we saw, CSS3 Transitions is a really easy way to add animations to your project. You can produce a more reactive application just by using some transitions when you want to change values.
By the way, there are two solutions if you want to implement a JavaScript fallback:
- You can do all in the JavaScript side and if you detect the support of CSS3 transitions, you will inject CSS3 declarations in the page.
- Or you can use standard way (using true CSS3 declarations in the CSS files) and just detect the need of fallback in JavaScript. For me, it is the better option as the fallback must be an option and not the main subject. In a near future, all browsers will support CSS3 Transitions and in this case you will just have to remove your fallback code. Furthermore, it is a better way to let all the CSS under the control of the creative team and not in the code part.
Going further