Version 1.0
Oct 14, 2009
Welcome to the Starlight developer guide - an interactive web development tutorial!
This is a technical developer guide explaining the core features of the Nokia Starlight browser. The guide is intended for developers who are already familiar with the basics of web technologies HTML, CSS and JavaScript. If you are new to JavaScript and CSS, good tutorials can be found at W3schools.com.
This is an interactive guide that actually allows you to run the presented examples with the browser you are using to read it. You do not need to copy and paste the source code; instead the guide allows you to modify and run the code instantly.
As Starlight is a set of different user interface creation technologies, the guide is divided into chapters. Each chapter covers a selected feature using concrete interactive examples.
At the end of the guide, there are real world examples combining multiple technologies.
The source code listings are syntax-colored to make them easy to read, and the code can be directly copied and pasted to your own application.
All the examples of the guide are also available as separate example pages
JavaScript Frameworks
This guide explains CSS and JavaScript use without frameworks. We believe that understanding the fundamentals of the CSS effects will be very useful, even while working with frameworks. As an increasing number of web developers do use JavaScript frameworks, we have worked closely with script.aculo.us author Thomas Fuchs, to make sure Starlight can be used with it and other JavaScript frameworks.
AUTOMATIC BROWSER DETECTION: You are using a browser that has support for CSS effects. Touch examples will not work, but CSS examples will work. For touch examples, you would need to get Starlight browser.
AUTOMATIC BROWSER DETECTION: You are using a browser that has support for Touch & CSS effects. Examples will work.
AUTOMATIC BROWSER DETECTION: You are using a browser that supports neither CSS effects nor Touch events. The examples in this guide will not function properly. Please see below how to obtain a suitable browser.
Nokia Starlight browser
Nokia Starlight Browser is currently the only browser that has full support for all the examples in this guide, and is therefore recommended to be used.
Running the CSS examples requires a modern browser based on the WebKit rendering engine, such as Apple Safari or Google Chrome. You can, however, view and read the guide with any browser.
Starlight is a combination of technologies enabling major improvements to the web user experience, with minor improvements to the existing web technologies.
To quickly demonstrate Starlight features, take a quick look at this little game that we created using the features of the Starlight browser. (Source files here: JavaScript and CSS.)
We believe that the new techniques showcased by the game can be use to build better web sites and applications in the touch-enabled devices of the future.
The Starlight browser supports both single and multitouch events, and has an extensive touch event API for capturing touch events.
The touch API can report both raw touch events for each finger, and higher-level manipulation events that are fired when multiple fingers operate on an element. Manipulation events provide data values such as panning, scaling and rotation conveniently as event properties, so the application developer does not have to calculate these values from raw touch events.
Event handlers are registered in JavaScript code simply with the W3C standard
element.addEventListener(eventtype, callbackfunc, usecapture) function.
There are no new JavaScript methods to be learned, just a few new event types.
For compatibility reasons, the touch events are also reported as mouse events,
even though there is no mouse in a touch device. Normal mouse events
mousedown, mousemove and mouseup are fired along with touch events, so
existing web applications also work in a touch only device.
Read the API below and play with the examples.
Below is the touch events API in the Starlight browser. Touch events are
fired whenever a finger or a stylus touches or moves on the screen. Multiple
fingers are separated with identifiers (event.id). The identifier of a
finger stays the same from finger press until the finger is lifted. Note that fingers
do not have fixed identifiers; the browser doesn't really know which of the
ten fingers touched the screen.
The three new touch event listeners for events fired when a
finger touches the element, are touchdown, touchmove and touchup (on the
left). The event object passed to the function has the properties on the
right:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Finger or a stylus touches the touch surface.
element.addEventListener('touchdown', function(event) {
...
}, false);
// Finger or stylus is moved.
element.addEventListener('touchmove', function(event) {
...
}, false);
// Finger or stylus is lifted off the touch surface.
element.addEventListener('touchup', function(event) {
...
}, false);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Touch id: each touch point has its own id of type integer
// The id remains the same from touchdown to touchup
event.id
// The horizontal & vertical coordinates at which the event
// occurred relative to the origin of the document
event.pageX
event.pageY
// The horizontal & vertical coordinates at which the event
// occurred relative to the origin of the screen
event.screenX
event.screenY
// The horizontal & vertical coordinates at which the event
// occurred relative to the origin of the viewport
event.clientX
event.clientY
// Standard modifier keys are included
event.ctrlKey
event.shiftKey
event.altKey
event.metaKey
// Pressure of the touch. Values are normalized on range 0.0 .. 1.0.
// If pressure is not supported, 1.0 is reported
event.pressure
// Bounding box of the touch, width and height
// If bounding box is not supported, 0 is reported
event.rectWidth
event.rectHeight
|
In addition to the raw touch events, there are also a set of higher level, usually more useful, manipulation events. Manipulation events are fired whenever an element is being manipulated on the screen, with either one or multiple fingers.
With manipulation events, the system interprets the gestures such as pan, scaling and rotation of elements.
Pan coordinates are activated even with a single finger. Scale and rotation values activate when multiple fingers touch the screen. If just a single finger touches the screen, scale is 1.0 and rotation is 0. The unit of rotation is radians (2*Pi radians for a full circle). Note that CSS transforms use degrees.
Pan coordinates are always relative to the start of the touch. Thus there is no need to calculate absolute offsets of elements on the page when moving an element. Pan values can only be added to the starting position of an element.
On the left, there are the 3 new manipulation event listener names for events that are fired when a finger touches the element - manipulatestart, manipulatemove
and manipulateend.
On the right, there are the the manipulate properties of the event parameter given to listener functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // Manipulation starts.
element.addEventListener('manipulatestart', function(event) {
// event.scale = 1.0
// event.rotation = 0.0
// event.panX = panY = panSpeedX = panSpeedY = 0
...
}, false);
// Manipulation continues.
element.addEventListener('manipulatemove', function(event) {
...
}, false);
// Manipulation ends.
element.addEventListener('manipulateend', function(event) {
// event attributes are the same as in last manipulatemove
...
}, false);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // NOTE: the touch properties mentioned above exist too,
// apart from id and rectX/Y
// The horizontal coordinate in client coordinate space that
// indicates the panning of the element.
event.panX
// The vertical coordinate in client coordinate space that
// indicates the panning of the element.
event.panY
// Panning speed (X/Y) in pixels/s in client coordinate space
event.panSpeedX
event.panSpeedY
// Scale factor, greater than 0.0 , smaller than 1.0 for scale down,
// greater than 1.0 scale up.
// E.g. 2.5 when the distance of touch points has increased 2.5-fold
// i.e. element should be scaled 2.5 times its original size
event.scale
// Speed of scaling in scale factor change / second.
event.scaleSpeed
// Rotation angle in radians, increasing angle
// meaning rotation clockwise.
event.rotation
// Rotation speed in radians/second.
event.rotationSpeed
|
This example demonstrates a simple and common scenario with touch: panning (or
dragging) elements on the screen.
In this example, we take advantage of pan coordinates provided by
manipulate* events. At the beginning of the drag, we store the initial
position of the element (CSS left and top). During the drag, pan coordinates
are simply added to the initial position in order to get the new position to move to.
A CSS style "dragged" is assigned for an element during drag to provide a nice visual indication of an active drag.
To start the example, apply both CSS and JavaScript.
Note that currently multiple simultaneous manipulate event streams are not supported, which means that you can't pan both boxes at the same time. If you require such functionality, but don't need scaling and rotation, you can use raw touch events instead.
This example requires the Starlight browser.
1 2 3 4 | <div id="bigbox3">
<div class="mybox">1</div>
<div class="mybox mybox2">2</div>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* outermost div, fills the whole black box below */
div#bigbox3 {
width: 100%; height: 100%;
}
/* box to be dragged */
div.mybox {
position: absolute;
left: 200px; top: 70px;
width: 80px; height: 80px;
color: white; background: green;
text-align: center;
border: 2px solid white;
font-size: 40px;
/* do not select text while dragging */
-webkit-user-select: none;
}
/* second box to be dragged */
div.mybox2 {
left: 500px; top: 90px;
}
/* style when being dragged */
div.dragged {
background: yellow;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // get handles to 2 DOM objects
var bigbox = document.getElementById("bigbox3")
var divs = bigbox.getElementsByTagName("div");
// associate manipulate listeners
for (var i = 0; i < divs.length; i++) {
var elem = divs[i];
elem.addEventListener('manipulatestart', function(e) {
e.preventDefault();
addClass(this, "dragged");
// remember starting position
this.oldPos = getPos(this);
}, false);
elem.addEventListener('manipulatemove', function(e) {
e.preventDefault();
// calculate new position: add pan to start position
var x = this.oldPos.left + e.panX;
var y = this.oldPos.top + e.panY;
this.style.left = x+"px";
this.style.top = y+"px";
}, false);
elem.addEventListener('manipulateend', function(e) {
e.preventDefault();
removeClass(this, "dragged");
}, false);
}
|
This example builds on the previous example not only to demonstrate pan, but
also to show how to use the manipulate* events to zoom and rotate an image
on the screen. Scale and rotate values of an event are applied to an image
with a CSS transform, and reported also in textual format on the right side.
Note that you need to use at least two fingers to activate scale and rotation.
To start the example, apply both CSS and JavaScript.
1 2 3 4 | <div id="bigbox">
<img id="map" src="map.png"/>
<p id="debug">debug here</p>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 | /* outermost div, fills the whole black box below */
div#bigbox {
width: 100%; height: 100%;
}
img#map {
position: absolute;
left: 20%; top: 10%;
border: 8px solid #f44;
}
p#debug {
float: right;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // get handles to DOM objects
var bigbox = document.getElementById("bigbox");
var map = bigbox.getElementsByTagName("img")[0];
var debug = bigbox.getElementsByTagName("p")[0];
// catch the manipulate events
map.addEventListener('manipulatestart', function(e) {
e.preventDefault();
// remember starting position
this.oldPos = getPos(this);
// remember starting transform
var t = getComputedStyle(this).webkitTransform;
this.oldMatrix = new WebKitCSSMatrix(t);
}, false);
map.addEventListener('manipulatemove', function(e) {
e.preventDefault();
transform(e);
}, false);
// transform the map
function transform(e) {
// calculate new position: add pan to start position
var x = map.oldPos.left + e.panX;
var y = map.oldPos.top + e.panY;
map.style.left = x+"px";
map.style.top = y+"px";
// calculate and set new matrix
var m = map.oldMatrix.rotate(0,0,e.rotation*360/2/3.1415).scale(e.scale);
map.style.webkitTransform = m;
debug.innerHTML = m;
}
|
This example demonstrates how to capture the raw touch events of multiple fingers in the Starlight browser.
When a finger touches div#bigbox2 inside the black box below, a div.b box
is added inside the div, and the added box then follows the movement of the
finger. Each box displays the finger ID associated with it. When the finger
is removed from the screen, the box attached to a finger gets removed from the
div.
Mouse events are captured too, and you will see their relation to touch events. Mouse events are fired along with touch events. Mouse events do not have an event ID; hence, "mouse" is displayed.
To start the example, apply both CSS and JavaScript.
1 2 3 4 5 | <div id="bigbox2">
<!-- div.b elements added dynamically here -->
</div>
<p id="debugmouse">mouse events here</p>
<p id="debugtouch">touch events here</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /* outermost div, fills the whole black box below */
div#bigbox2 {
width: 100%; height: 100%;
}
/* little box under finger */
div.b {
position: fixed;
left: 0; top: 0;
width: 50px; height: 50px;
color: white; background: red;
text-align: center;
border: 2px solid white;
/* do not select text while dragging */
-webkit-user-select: none;
}
/* make mouse box look different and offset a little bit,
so it can be seen next to a finger */
div#mouse {
background: green;
margin: -15px 0 0 -15px;
}
/* display debug info on the right */
p#debugmouse {
position: absolute;
top: 0; right: 5px;
color: green;
}
p#debugtouch {
position: absolute;
right: 5px; top: 20px;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | // get handle to DOM object
var dombox = document.getElementById("bigbox2");
var BOXW=35, BOXH=35;
// count of fingers currently active
var tcount = 0;
// capture normal mouse events: down,move,up
var mouseid = "mouse";
dombox.addEventListener('mousedown', function(e) {
print_mouse("mousedown", e);
delBox(mouseid);
addBox("mouse", mouseid);
moveBox(mouseid, e.clientX, e.clientY);
}, false);
dombox.addEventListener('mousemove', function(e) {
print_mouse("mousemove", e);
moveBox(mouseid, e.clientX, e.clientY);
}, false);
dombox.addEventListener('mouseup', function(e) {
print_mouse("mouseup", e);
delBox(mouseid);
}, false);
// capture touch events: down,move,up
dombox.addEventListener('touchdown', function(e) {
tcount++;
print_touch("touchdown", e);
delBox("touch "+e.id);
addBox("touch "+e.id, e.id);
moveBox(e.id, e.clientX, e.clientY);
}, false);
dombox.addEventListener('touchmove', function(e) {
print_touch("touchmove", e);
moveBox(e.id, e.clientX, e.clientY);
}, false);
dombox.addEventListener('touchup', function(e) {
tcount--;
print_touch("touchup", e);
delBox(e.id);
}, false);
// Prevent scrolling and zooming while touching this area
dombox.addEventListener('manipulatestart', function(e) {
e.preventDefault();
}, false);
dombox.addEventListener('manipulatemove', function(e) {
e.preventDefault();
}, false);
dombox.addEventListener('manipulateend', function(e) {
e.preventDefault();
}, false);
// add, move and remove boxes for each finger
function addBox(title, id) {
var newdiv = document.createElement('div');
newdiv.setAttribute("id", id);
newdiv.setAttribute("class", "b");
newdiv.appendChild( document.createTextNode(title));
dombox.appendChild(newdiv);
return id;
}
function moveBox(id, x, y) {
var elem = document.getElementById(id);
if (elem) {
elem.style.left = (x-BOXW)+"px";
elem.style.top = (y-BOXH)+"px";
}
}
function delBox(id) {
var elem = document.getElementById(id);
if (elem)
elem.parentNode.removeChild(elem);
}
// print debug output
function print_mouse(s, e) {
var x = " ("+(e.clientX)+" x "+(e.clientY)+")";
document.getElementById("debugmouse").innerHTML = s+x;
}
function print_touch(s, e) {
var x = " ("+e.clientX+" x "+e.clientY+") - fingers: "+tcount;
document.getElementById("debugtouch").innerHTML = s+x;
}
|
The contemporary web development typically manages the actions on a web page by changing the values of CSS properties dynamically via JavaScript. An HTML element can be moved to another location, made bigger, have its color changed, etc. by assigning a new value to its respective CSS property via JavaScript. The new value becomes effective immediately creating a visual effect. However, there is still a lot of room for improvement in the effects to support direct manipulation and smooth user experience.
CSS transitions provide a mechanism to animate changes in CSS with a smooth transition. When a CSS property is changed from value A to value B (typically through assigning new CSS properties to the DOM element with javascript), the browser computes all the intermediate values of A and B, and visualizes the change over time, giving the user nice feedback for actions taking place on the page. As a result, the user experience becomes more pleasant.
If you are new to CSS, you can learn the basics about CSS properties at W3schools.com, for example.
CSS transitions are declared by defining the CSS properties to which a smooth transition is applied.
A few new CSS properties are introduced to name the transitioned properties. They are prefixed with -webkit-, as they are still extensions in certain
WebKit-based browsers, such as Chrome, Safari and many of the mobile browsers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | div.myclass {
/* shorthand notation: property name and duration together */
-webkit-transition: <css-property> <duration> [,..];
/* longhand notation, separate property name and duration */
-webkit-transition-property: <css-property> [,..];
-webkit-transition-duration: <duration> [,..];
/* start transition after a delay */
-webkit-transition-delay: <time-value>;
/* easing: how to change in relation to time */
-webkit-transition-timing-function: <functioname>
}
|
Once the declaration is done, a transition is simply initiated whenever a new
value is assigned to a CSS property of an HTML element, either via CSS class
addition or a direct element.style.property = newvalue modification by
JavaScript. There is no separate API to start a transition.
CSS properties with scalar values such as dimensions and locations, colors and opacity are transitionable. The browser is smart enough to know how to transition different properties.
Note that the actual CSS transition is performed natively by the browser and not through JavaScript routines, like several contemporary JavaScript frameworks normally do. Native implementation makes the transition more effective and thus suitable for handheld devices, where the resources are usually more limited than on the desktop.
Since transitions can be introduced easily via CSS files, and they are activated via a simple JavaScript value assignment (the usual way of creating dynamic UI in web applications), it is easy to add CSS transitions even to existing web applications.
The examples in this chapter will show in detail how the CSS transition properties are used in practice.
Below is a basic example of how CSS transitions work. The example shows two
elements div.boxold and div.boxnew being moved to the right. The first
box is moved in the old way, without a transition, and the other box is moved
with a transition. The effect of a transition should become very clear after
running the example.
First apply CSS-A. To move boxes, then apply CSS-B, which is appended after CSS-A. Both of the CSS thus become effective.
Apply and remove CSS-B to replay the effect.
Notice that the code heading turns black when the CSS is applied. This gives you a visual cue of whether the CSS is on or off. This practice applies to all examples in this guide.
Feel free to play more with the example. Modify and apply the CSS properties, and see the effects directly in your browser.
A tip: if you want to transition all possible CSS properties,
use -webkit-transition: all in the CSS.
1 2 3 4 5 6 | <div class="boxold">
No transition.
</div>
<div class="boxnew">
Using transition!
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | div.boxold {
position: absolute;
left: 40px; top: 120px;
width: 120px; height: 60px;
border: 1px solid red;
background: gray;
}
div.boxnew {
position: absolute;
left: 140px; top: 130px;
width: 120px; height: 60px;
border: 1px solid red;
background: gray;
/* apply 2s transition to these CSS properties: */
-webkit-transition: width, height, left,
top, border, background;
-webkit-transition-duration: 2s;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | div.boxold {
/* move the old box */
left: 400px; top: 10px;
width: 300px; height: 120px;
border: 10px solid white;
background: green;
}
div.boxnew {
/* move the new box */
left: 500px; top: 40px;
width: 300px; height: 120px;
border: 10px solid white;
background: green;
}
|
The duration of a transition is controlled by -webkit-transition-duration
CSS property. In addition to duration, it is possible to further control how
the value changes over the period of a transition: does the value change
linearly from A to B, or does the value change faster in the beginning and
slower at the end, etc. The start and end values A and B do not change,
it is all about how the journey between the values is performed.
The velocity of the change of a value over the transition period is called
easing. Easing is controlled by -webkit-transition-timing-function CSS
property. This property accepts a JavaScript function name. There are also a few predefined functions available, like "linear", "ease", "ease-out" and "ease-in-out"
If you specify no timing function, the default function is ease, which
accelerates at the beginning and decelerates at the end of transition. It is
similar to ease-out, but more aggressive in the middle. See the
example below.
Fundamentally, all easing functions can be expressed with a cubic-bezier
function, which is a mathematical curve that has two control points that are
specified for the easing function. The last box in the example shows an
cubic-bezier function in action. For more information about bezier curves, see
Wikipedia article or
AMS article.
In the example below, first apply CSS-A and then CSS-B, and see the difference of the easing functions. Toggle CSS-B to move boxes back and forth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <div class="movebox">
default
</div>
<div class="movebox linear">
linear
</div>
<div class="movebox easein">
ease-in
</div>
<div class="movebox easeout">
ease-out
</div>
<div class="movebox easeinout">
ease-in-out
</div>
<div class="movebox cubic">
cubic-bezier(0.5,1,1,0)
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | div.movebox {
position: relative;
left: 0;
width: 20%; height: 28px;
border: 2px solid white;
background: green;
/* transition a single CSS property: left */
-webkit-transition: left 3s;
}
div.linear {
-webkit-transition-timing-function: linear;
}
div.easein {
-webkit-transition-timing-function: ease-in;
}
div.easeout {
-webkit-transition-timing-function: ease-out;
}
div.easeinout {
-webkit-transition-timing-function: ease-in-out;
}
div.cubic {
-webkit-transition-timing-function: cubic-bezier(0.5,1,1,0);
}
|
1 2 3 4 | /* a single target for all boxes: move to the right */
div.movebox {
left: 80%;
}
|
Sometimes it is necessary to know when a transition ends; to update web application state, or to chain multiple transitions together by starting a second transition after the first one ends.
The end of a CSS transition can be captured by a JavaScript event listener:
(To learn more about addEventListener, see
W3C specification.)
1 2 3 4 5 | /* Register event listener for transition end */
element.addEventListener('webkitTransitionEnd', function(event) {
/* event.propertyName reports the property name */
...
}, false);
|
Note that the event is fired once for each transitioned CSS property. That
is, if you transition both left and top CSS properties, for example, you will
get two webkitTransitionEnd events at the end of transitions. The events may
fire at different times, hence the duration can be set individually for each CSS
property.
Also, note that if you assign the same current value again into a CSS
property, the transition will not start, and there will be no
webkitTransitionEnd event.
This example uses CSS and JavaScript. CSS is used to specify two states: the
start and end state of the transition. The best way to define states is to
assign a separate CSS class for each state. div.mybox defines the starting
state and div.myboxright defines the end state. JavaScript is then used to
assign class names accordingly. This is a clean way to implement web
applications, honoring the good practice of separation of presentation (CSS)
and behavior (JavaScript).
First apply CSS, and then apply JavaScript. Then click on the green box. A
JavaScript click event handler adds a class .myboxright to the box. (The
utility function addClass() is listed in the appendix.) Note that after
the click the div has two active classes: .mybox .myboxright, and as a result,
the box transitions to the right and changes its opacity at the same time.
At the far right, the transition ends, and a webkitTransitionEnd event is
fired. JavaScript then removes the .myboxright class from the box, and the
box starts its transition back to the left.
Note that the two transitioned properties left and opacity have different
durations: 2s and 1.3s. There will thus be two webkitTransitionEnd events at
different times. Only when the longer transition left ends, the box is set
to travel back to left.
A debug statement at the right of the black box reports some state information of the example.
If you want to experiment with different transition starting delays, uncomment
-webkit-transition-delay from the CSS.
1 2 3 4 | <div id="mybox" class="mybox">
mybox
</div>
<p id="debug3">debug message here</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* moving box */
div.mybox {
position: absolute;
left: 10%; top: 70px;
width: 20%; height: 80px;
color: white; background: green;
border: 2px solid white;
/* transition left and opacity properties */
-webkit-transition: left 2s, opacity 1.3s;
/* start transition after a delay */
//-webkit-transition-delay: 0.5s;
}
/* move to the right, this class added with JavaScript */
div.myboxright {
left: 70%;
opacity: 0.3;
}
/* for displaying debug info */
p#debug3 {
float: right;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // get handles to DOM objects
var mybox = document.getElementById("mybox");
var debug = document.getElementById("debug3");
mybox.innerHTML = "Click me!";
// set click listener
mybox.addEventListener('click', function(e) {
// start transition
addClass(this, "myboxright");
debug.innerHTML = "click!";
}, false);
var counter = 0; // count transition end events
// set a listener for transition end
mybox.addEventListener('webkitTransitionEnd', function(e) {
counter++;
debug.innerHTML = "transition end "+counter+
", property: "+e.propertyName;
if (e.propertyName == "left") {
/* start transition back ('left' and 'opacity'
set back to original values) */
removeClass(this, "myboxright");
}
}, false);
|
Unfortunately there is no explicit way to stop or cancel a transition.
However, it is possible to stop a transition in the middle, but it is a bit of a hack:
To stop a transition, i.e. to freeze the transitioned CSS property into
its current value, you need to assign the current value as the new end
value. Finding the current intermediate value, or computed value, during a
transition is done with the document.defaultView.getComputedStyle()
JavaScript function.
But there is a catch: For instance, in the example below, where the CSS
property left is transitioned, the unit must be pixels. Using percentages
does not work, because a percentage unit is not accurate enough, the element
still continues to move slowly after a stop. You can try yourself by playing
with percentages in the example.
In this example, move the box left and right by clicking the buttons, and stop the transition by clicking on the box itself.
1 2 3 4 5 6 | <div id="mybox4" class="mybox4">
mybox
</div>
<input type="button" id="butleft" value="Go Left"/>
<input type="button" id="butright" value="Go Right"/>
<p id="debug4">debug message here</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* moving box */
div.mybox4 {
position: absolute;
left: 0; top: 70px;
width: 20%; height: 80px;
color: white; background: green;
border: 2px solid white;
/* transition a single property */
-webkit-transition: left 5s;
}
/* for displaying debug info */
p#debug4 {
float: right;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // get handles to DOM objects
var mybox = document.getElementById("mybox4");
var debug = document.getElementById("debug4");
var counter = 0; // click counter
mybox.innerHTML = "Click to stop me!";
// set click listener
mybox.addEventListener('click', function(e) {
// calculate current value of 'left'
var computedStyle = document.defaultView.getComputedStyle(mybox, null);
var curLeft = parseInt(computedStyle.getPropertyValue("left"));
// assign it as new transition target
mybox.style.left = curLeft+"px";
debug.innerHTML = "stopped! left "+curLeft+ "px, count "+counter++;
}, false);
document.getElementById("butleft").addEventListener('click', function(e) {
mybox.style.left = "10px";
debug.innerHTML = "moving left";
}, false);
document.getElementById("butright").addEventListener('click', function(e) {
mybox.style.left = "500px";
debug.innerHTML = "moving right";
}, false);
|
CSS animations are similar to CSS transitions; they aim to provide visually pleasant transitions of CSS property changes in the web application. CSS animations differ from transitions in that CSS animations provide a more declarative and fine-grained approach with control for in-between states of the transition.
The declarative approach of CSS animations means that the whole animation and its intermediate steps are declared fully with CSS. The syntax of the CSS has been extended slightly to allow the specification of animation keyframes.
CSS animations also provide a finer-grained control for the intermediate states of the animation. CSS transitions only animate between the start and end values of the CSS property, following the specified easing function. With CSS animations, it is possible to define any number of intermediate states, called keyframes, of the animation, not just single start and end values. Each frame can specify its own list of animated CSS properties and easing function. The browser calculates the intermediate values between frames, and displays the animation.
In short, CSS animations provide a more explicit and staged mechanism to animate elements on the screen.
However, the CSS animations are not necessarily better than CSS transitions. CSS animations lack some of the flexibility of CSS transitions; CSS animations are somewhat static since they are declared in CSS, and not calculated dynamically. And because CSS animation properties do not stay effective when the animation ends, the use of CSS animations versus transitions needs to be considered for each case. Transitions work well as page element effects, while animations are good for creating an animation to a page.
Here are the available CSS animation properties and the syntax for declaring key frames.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | div.myclass {
/* list of animations to apply to this element */
-webkit-animation-name: <animname> [,..];
/* duration of a single animation cycle */
-webkit-animation-duration: <duration> [,..];
/* start animation after a delay */
-webkit-animation-delay: <time-value>;
/* number of animation cycles
'infinite' means forever */
-webkit-animation-iteration-count: <count>;
/* are odd animation cycles played in reverse?
'normal' means all cycles run forward
'alternate' means odd cycles run backward */
-webkit-animation-direction: 'normal' or 'alternate';
/* how to change in relation to time,
same as CSS transition timing function */
-webkit-animation-timing-function: functioname;
/* shorthand notation: all 6 above properties expressed
in a single line */
-webkit-animation: <six above properties combined>;
/* state of the animation, set to 'paused' to pause
an animation */
-webkit-animation-play-state: 'running' or 'paused';
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* keyframes declared with extended syntax: */
@-webkit-keyframes 'animname' {
/* anim start keyframe (name: '0%' or 'from') */
0% { left: 0; }
/* anim intermediate keyframes, any number of,
always start with a percentage */
33% { left: 150px; }
67% { left: 75px; }
/* anim end keyframe (name: '100%' or 'to') */
100% { left: 0; }
}
/* Note: only 'left' animated above, for simplicity */
|
The animation is declared with a named keyframes declaration that is a set of keyframes. The extended syntax can be seen above on the right. At least two keyframes must exist inside the animation, 0% and 100% (They also have aliases, 'from' and 'to' respectively.) Between 0% and 100%, there can be any number of keyframes. All keyframes must start with a percentage value that specifies the relative position of a keyframe inside the whole animation. A single frame can contain any number of CSS properties to be animated.
Easing function names are the same as for CSS transitions. It is also
possible to change the easing function between keyframes with the
-webkit-animation-timing-function CSS property. (Other -webkit-animation-*
properties cannot be declared inside a keyframe, only in a class declaration.)
A CSS animation is started by attaching the animation name to an element.
This can be done via CSS by adding a class which has the
-webkit-animation-name property, or directly via JavaScript with
element.style.webkitAnimationName = 'name';.
When a CSS animation ends, the element returns to its original state that it had before the animation. All animated CSS properties from the keyframes thus disappear. This is different than in a CSS transition, where at the end the element is left at the final state it was transitioned to.
A CSS animation can be stopped during the animation by setting the animation
name to none, either via class removal or direct JavaScript:
element.style.webkitAnimationName = 'none';
When restarting an animation, you should take the CSS flush into account, changes take place only after the event handler has finished (see below).
CSS animation also provides control points for JavaScript. Specified events can be captured by JavaScript event listeners. There are three different events that can be listened for:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* Register event listener for start of animation */
element.addEventListener('webkitAnimationStart', function(event) {
/* event.animationName reports the animation name */
...
}, false);
/* Register event listener for end of each cycle */
element.addEventListener('webkitAnimationIteration', function(event) {
/* event.animationName reports the animation name */
...
}, false);
/* Register event listener for end of animation */
element.addEventListener('webkitAnimationEnd', function(event) {
/* event.animationName reports the animation name */
...
}, false);
|
To read more about CSS animations, you can refer to the W3C working draft. (Note that in the draft, the CSS properties do not have the -webkit prefix.)
This is a simple example of a CSS animation in action. An animation named
goaround is declared in CSS. The animation has 6 defined keyframes that
adjust CSS properties left, top and background color at various stages.
When you click on the green box, the animation is started by adding the class
myanim to a box. This class specifies the name of the animation to start:
goaround. Once started, the box moves around the big black box along the
edges of the black box, taking 3 seconds to finish.
For learning purposes, the example has an odd value in the code. Notice how at the end of the animation the box seems to jump and not slide smoothly to its original position in the top left-hand corner. The odd value demonstrates how the animation properties seize to exist after an animation ends. You can play with the source code and fix the value.
There is one important point in the click event handler: dealing with the CSS
flush. Restarting the animation first requires removing myanim
class, and then adding the myanim class again from a timeout. It is not
possible to add the class directly after remove, since it would not provide
any effect. The reason for this is that all CSS operations are flushed only at
the end of the event handler. CSS needs to be "cleared" at the boundary of
different effects.
This CSS flush technique, setting CSS properties in a setTimeout
JavaScript callback, is used throughout the examples in this guide. This is essential in managing the CSS flush.
1 2 3 | <div id="mybox5" class="mybox5">
Hello, Animation!
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* animated box */
div.mybox5 {
position: absolute;
left: 0; top: 0;
width: 20%; height: 20%;
color: white; background: green;
border: 2px solid white;
}
/* start animation, this class added with JavaScript */
div.myanim {
/* name of animation to start */
-webkit-animation-name: 'goaround';
/* duration of animation */
-webkit-animation-duration: 3s;
}
/* keyframes for my animation */
@-webkit-keyframes 'goaround' {
0% { }
25% { top: 80%; }
37% { left: 40%; top: 30%; background: red; }
50% { left: 80%; top: 80%; }
75% { left: 80%; background: blue; }
100% { left: 20%; /* notice end value! */ }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // get handle to DOM object
var mybox = document.getElementById("mybox5");
mybox.innerHTML = "Click me!";
// set click listener
mybox.addEventListener('click', function(e) {
// start the animation!
// first remove all animations
removeClass(mybox, "myanim");
// then flush CSS, and finally apply anim
setTimeout(function() {
addClass(mybox, "myanim");
}, 0);
}, false);
|
This example demonstrates the use of several CSS animation properties and all three of the animation event callbacks. Readers familiar with the TV show Knight Rider might experience a déjà vu. Click the red box after applying CSS and JavaScript.
A CSS animation named sweep is declared in CSS. This has only 3 keyframes
with an increasing values for CSS property 'left' (plus some other). This
animation thus moves an element from left to right. In the demo, however, the
box clearly moves from left to right and back. How is this achieved? The
answer lies in the CSS: the animation is specified to take 2 cycles, with odd
cycles animating the keyframes backwards. See the code.
All the three CSS animation events are captured for learning purposes. See the debugging info on the right that displays the event data.
Note that unlike the CSS transition webkitTransitionEnd event, the
webkitAnimationEnd event reports the name of the CSS animation that ended,
not names of individual CSS properties.
At the end of each animation, the box is lowered by 20%, and the animation is restarted until the box is at the bottom. A click on the box at the bottom restarts the whole loop.
1 2 3 4 | <div id="mybox6" class="mybox6">
mybox
</div>
<p id="debug6">debug message here</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /* animated box */
div.mybox6 {
position: absolute;
left: 0; top: 0;
width: 20%; height: 20%;
color: white; background: red;
border: 2px solid white;
}
/* for displaying debug info */
p#debug6 {
float: right;
}
/* start animation, this class added with JavaScript */
div.myanim {
/* name of animation to start */
-webkit-animation-name: 'sweep';
/* duration of cycle, whole anim is 2x */
-webkit-animation-duration: 0.5s;
/* two cycles: back and forth */
-webkit-animation-iteration-count: 2;
/* second cycle is done backwards */
-webkit-animation-direction: alternate;
/* use linear movement */
-webkit-animation-timing-function: linear;
}
/* keyframes for my animation */
@-webkit-keyframes 'sweep' {
0% { }
50% { left: 40%; background: black; border: none; }
100% { left: 80%; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // get handles to DOM objects
var mybox = document.getElementById("mybox6");
var debug = document.getElementById("debug6");
mybox.innerHTML = "Click me!";
var h; // current y position of box
// set click listener
mybox.addEventListener('click', function(e) {
// start from top
mybox.style.top = "0%"; h = 0;
// start the animation!
startAnim();
}, false);
// starts and restarts an animation
function startAnim() {
removeClass(mybox, "myanim");
setTimeout(function() {
addClass(mybox, "myanim");
}, 0);
}
// set a listener for anim start, only prints debug
mybox.addEventListener('webkitAnimationStart', function(e) {
printDebug("start", e);
}, false);
// set a listener for anim iter, only prints debug
mybox.addEventListener('webkitAnimationIteration', function(e) {
printDebug("iteration", e);
}, false);
// set a listener for anim end
mybox.addEventListener('webkitAnimationEnd', function(e) {
printDebug("end", e);
// move box down by 20% until at bottom
h=h+20;
if (h < 100) {
mybox.style.top = h+"%";
startAnim();
}
}, false);
// print debug info
var counter = 0;
function printDebug(eName, e) {
counter++;
debug.innerHTML = "anim "+eName+", "+counter+
", name: "+e.animationName;
}
|
CSS transforms provide a powerful mechanism for manipulating the visual appearance of HTML elements. Visual effects such as scaling, rotation, translation, skew and advanced 3D effects can be applied. Transforms enable quite remarkable visual effects in HTML/CSS/JavaScript without the need for external plugins.
CSS transforms can be used for visual appeal, but also to detailed layout management, such as displaying text vertically or rotating a picture.
The basic effects of CSS transforms can be understood and applied easily, as the following examples demonstrate. However, since the transform framework is very powerful, a thorough understanding requires some knowledge of mathematics. This guide will not go into the mathematical details, but the basic concepts are explained, and the examples in this guide attempt to provide a playground for better understanding the concept by experimenting.
At the core of CSS transforms is the concept of coordinate spaces. In a 2D world, the coordinates are in X and Y axis, extending from left to right and from top to bottom. All the CSS transforms do is manipulate the coordinate space of an element. CSS transforms do not provide any new visual building blocks, such as borders or colors, they just "twist" an already visualized element. In technical terms, a local coordinate space is created for an element, and the coordinates are manipulated with a specified CSS transform function to make the element look rotated or scaled, for example.
At first it is easy to confuse CSS transforms with CSS transitions, which were explained earlier in this guide. The names are indeed quite similar, but the concepts are different: a CSS transform is a static adjustment of the appearance of an element (its coordinate space), while a CSS transition is a mechanism to animate CSS changes of an element over a period of time. They are separate things, but can be combined together, by animating the change of a transform. This is demonstrated in the examples below.
There are several new CSS properties to apply and control a CSS transform.
However, the single most important property is -webkit-transform, which
lists the transforms that are to be applied for an element. Other properties
are in a supporting role.
Note that the since the 3D support is still somewhat new and evolving, not all 3D properties are supported by all browsers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | div.myclass {
/* list of transforms that apply to this element */
-webkit-transform: funcname [,..];
/* set the coordinate origin for this element */
-webkit-transform-origin: x-pos y-pos;
/* 3D properties */
/* amount of perspective, "height" of the eye from plane;
smaller values provide a bigger 3D effect */
-webkit-perspective: number;
/* origin of the 3D effect; default is in the middle
of the element */
-webkit-perspective-origin: number;
/* whether children of the element are flattened to the
parent's plane or if they preserve their own plane */
-webkit-transform-style: 'flat' or 'preserve-3d';
/* is the "back" of an element displayed as a
mirror image, or is the back hidden? */
-webkit-backface-visibility: 'visible' or 'hidden';
}
|
A CSS transform is used by declaring a transform function in CSS property
-webkit-transform in the CSS file, or directly setting the CSS property via
JavaScript: element.style.webkitTransform = value.
Transforms are defined by transform functions, each of which changes the shape of the element in predefined way. Available functions are listed below, divided into 2D and 3D categories.
Note that the since the 3D support is still somewhat new and evolving, not all 3D transform functions are supported by all browsers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | div.myclass {
/* translate: move along X and Y axis */
-webkit-transform: translate(10px, 10px);
/* translateX: move only along X axis */
-webkit-transform: translateX(10px);
/* translateY: move only along Y axis */
-webkit-transform: translateY(10px);
/* scale: scale both X and Y dimensions */
-webkit-transform: scale(2.0, 2.0);
/* scaleX: scale only X dimension */
-webkit-transform: scaleX(2.0);
/* scaleY: scale only Y dimension */
-webkit-transform: scaleY(2.0);
/* skew: skew both X and Y dimensions */
-webkit-transform: skew(45deg, 45deg);
/* skewX: skew only X dimension */
-webkit-transform: skewX(45deg);
/* skewY: skew only Y dimension */
-webkit-transform: skewY(45deg);
/* rotate: rotate around Z axis */
-webkit-transform: rotate(90deg);
/* matrix: specify the 2D transform with a 3x3 matrix */
-webkit-transform: matrix(1, 2, 3, 4, 5, 6);
}
/* Note: using real numbers above just as an example. */
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | div.myclass {
/* translate 3D: move along X, Y and Z axis */
-webkit-transform: translate3d(10px, 10px, 10px);
/* translateZ: move only along Z axis */
-webkit-transform: translateZ(10px);
/* scale 3D: scale all X, Y and Z dimensions */
-webkit-transform: scale3d(2.0, 2.0, 2.0);
/* scaleZ: scale only Z dimension */
-webkit-transform: scaleZ(2.0);
/* rotate 3D: rotate around all axis */
-webkit-transform: rotate3D(45deg, 45deg, 45deg);
/* rotateX: rotate around X axis */
-webkit-transform: rotateX(90deg);
/* rotateY: rotate around Y axis */
-webkit-transform: rotateY(90deg);
/* rotateZ: rotate around Z axis */
-webkit-transform: rotateZ(90deg);
/* perspective: amount of 3D effect, depth of viewpoint */
-webkit-transform: perspective(400);
/* matrix3d: specify the 3D transform with a 4x4 matrix */
-webkit-transform: matrix3d(16 values here!);
}
/* Note: using real numbers above just as an example. */
|
This example demonstrates the basics of 2D transforms. There are several green boxes that each apply its own transform function. CSS-A specifies the starting state with no transforms and CSS-B specifies the end state with the transforms applied.
For better visualization, a CSS transition is used to animate the result of a transform. You will see how the transform builds up from the beginning. CSS transitions are smart in that they can also visualize the intermediate steps of a complex CSS transform.
The last box has two transforms applied at the same time, scaleX and skew. Multiple transforms can be defined at the same time by separating the function names with a space.
To replay the transition, remove CSS and apply it again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <div class="box2d">
no transform
</div>
<div class="box2d boxtranslate">
translate( -40px, 10px)
</div>
<div class="box2d boxscale">
scale(1.6, 0.8)
</div>
<div class="box2d boxskew">
skew(5deg, -20deg)
</div>
<div class="box2d boxrotate">
rotate(40deg)
</div>
<div class="box2d boxmatrix">
matrix(1,0.3, -0.1,2, 10,30)
</div>
<div class="box2d boxmany">
multiple: scaleX(2.0) skew(5deg, 10deg)
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /* transformed box */
div.box2d {
position: absolute;
left: 0; top: 0;
width: 15%; height: 30%;
color: white; background: green;
border: 2px solid white;
font-size: 12px;
/* visualize transform with a CSS transition */
-webkit-transition: -webkit-transform 1s;
}
/* position boxes evenly across */
div.boxtranslate {
left: 25%;
}
div.boxscale {
left: 50%;
}
div.boxskew {
left: 0; top: 50%;
}
div.boxrotate {
left: 25%; top: 50%;
}
div.boxmatrix {
left: 50%; top: 50%;
}
div.boxmany {
left: 75%; top: 50%;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* transform rules */
div.boxtranslate {
-webkit-transform: translate(-40px, 10px);
}
div.boxscale {
-webkit-transform: scale(1.6, 0.8);
}
div.boxskew {
-webkit-transform: skew(5deg, -20deg);
}
div.boxrotate {
-webkit-transform: rotate(40deg);
}
div.boxmatrix {
-webkit-transform: matrix(1,0.3,-0.1,2,10,30);
}
div.boxmany {
-webkit-transform: scaleX(2.0) skew(5deg, 10deg);
}
|
CSS transforms are cumulative, meaning that there can be multiple transforms effective within each other. Nested elements inherit a transform from their ancestor, and can define a new transform that builds up from the transform of the ancestor. The final transform is a multiplication of all contained transforms.
In this example, you will see how each box adds 20 degrees to the rotation of its ancestor. As a result of transform cumulation, the innermost blue box is rotated for a total of 60 degrees in reference to the page.
A CSS transition is used to visualize the result of a transform.
1 2 3 4 5 6 7 8 9 | <div class="boxcumu boxcumu1">
first
<div class="boxcumu boxcumu2">
second
<div class="boxcumu boxcumu3">
third
</div>
</div>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* transformed box */
div.boxcumu {
width: 30%; height: 50%;
color: white; background: green;
border: 2px solid white;
/* visualize transform with a CSS transition */
-webkit-transition: -webkit-transform 1s;
}
div.boxcumu1 {
position: absolute;
left: 20%; top: 20%;
}
div.boxcumu2 {
width: 50%;
background: red;
}
div.boxcumu3 {
width: 50%;
background: blue;
}
|
1 2 3 4 5 6 7 8 9 10 | /* transform rules */
div.boxcumu1 {
-webkit-transform: rotate(20deg) scale(2.0, 2.0);
}
div.boxcumu2 {
-webkit-transform: rotate(20deg);
}
div.boxcumu3 {
-webkit-transform: rotate(20deg);
}
|
This example demonstrates the adjustment of the origin of a CSS transform.
Origin is a reference point that is fixed in the coordinate space of an element. A point located exactly at the reference point is not transformed in any way, and all the points around the origin are transformed according to the transform function.
For better visualization, this example uses a CSS animation named
scaleandrotate to first scale and then rotate the element around its origin.
The animation makes it clearer to see the location of the origin.
If the origin is not set, the default origin is always in the middle of an
element. Default is thus the same as specifying -webkit-transform-origin: 50%
50%;.
The middle of an element is a good default value for an origin, but other origins may be better, depending on the case. It is also possible to set the origin of the transform outside of the element. This is demonstrated by the last box on the right.
To replay the animation, remove CSS-B and apply it again.
1 2 3 4 5 6 7 8 9 10 11 12 | <div class="boxorigin">
default origin: middle
</div>
<div class="boxorigin origtopleft">
top left origin
</div>
<div class="boxorigin origbottomright">
bottom right origin
</div>
<div class="boxorigin origcustom">
origin -50%, -50%
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* rotated box */
div.boxorigin {
position: absolute;
left: 0; top: 20%;
width: 10%; height: 30%;
color: white; background: green;
border: 2px solid white;
}
/* adjust origins (change becomes visible only when
a transform is applied with CSS-B */
div.origtopleft {
left: 30%;
-webkit-transform-origin: top left;
}
div.origbottomright {
left: 60%;
-webkit-transform-origin: bottom right;
}
div.origcustom {
left: 90%;
-webkit-transform-origin: -50% -50%;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* apply CSS animation to visualize origins */
div.boxorigin {
/* visualize transform with a CSS animation */
-webkit-animation-name: 'scaleandrotate';
/* duration of cycle, whole anim is 2x */
-webkit-animation-duration: 4s;
/* two iterations */
-webkit-animation-iteration-count: 2;
/* use linear change */
-webkit-animation-timing-function: linear;
}
/* keyframes for my animation */
@-webkit-keyframes 'scaleandrotate' {
0% { }
40% { -webkit-transform: scale(2, 3); }
60% { }
80% { -webkit-transform: rotate(180deg); }
100% { -webkit-transform: rotate(360deg); }
}
|
This example demonstrates the basics of 3D transforms. There are several green boxes that each apply a 3D transform function with different perspectives. The first box has no transform function, and thus provides the reference for the other boxes.
For better visualization, a CSS transition is used to animate the result of a transform, like in the 2D example above.
As you see, the 3D transforms come alive only when the perspective(value)
transform is applied as the first transform function of an element. Without
it, there is no visible 3D effect. The perspective defines the "amount" of 3D
perspective. Smaller values provide a stronger effect. The value essentially
defines the height of the eye from the plane of elements. Smaller values mean
that the plane is closer to the eye, and thus the 3D effect more profound.
Note that it is not possible to position the eye freely above the plane in a 3D coordinate system.
Manipulation of the elements in the 3D-space is done with rotateX(), rotateY() and rotateZ() rotating the element around the respective axes to a desired angle, and possibly applying translateZ() moving the element towards the eye or away from the eye.
The boxes on the bottom row display how the elements can be set to stack
correctly in a 3D world with the -webkit-transform-style: preserve-3d;.
To replay the transition, remove CSS-B and apply it again.
You can find out more information in the webkit.org blog.
Note: Perspective and transform-style don't work with version 1.0 of Starlight browser. To see the correct results, try WebKit nightly builds on a Mac.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <div class="box3d">
no transform
</div>
<div class="box3d rotateY">
rotateY, no perspective
</div>
<div class="box3d rotateY2">
rotateY, perspective 400
</div>
<div class="box3d rotateY3">
rotateY, perspective 100
</div>
<div class="box3d preserve3d1">
no preserve3d
<div>child</div>
</div>
<div class="box3d preserve3d2">
preserve3d
<div>child</div>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /* transformed box */
div.box3d {
position: absolute;
left: 0; top: 0;
width: 15%; height: 30%;
color: white; background: green;
border: 2px solid white;
/* visualize transform with a CSS transition */
-webkit-transition: -webkit-transform 1s;
}
/* position boxes evenly across */
div.rotateY {
left: 20%;
}
div.rotateY2 {
left: 40%;
}
div.rotateY3 {
left: 60%;
}
div.preserve3d1 {
left: 0%; top: 50%;
}
div.preserve3d2 {
left: 20%; top: 50%;
}
div.box3d div {
width: 80%; height: 50%;
border: 2px solid black;
background: red;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* transform rules */
div.rotateY {
-webkit-transform: rotateY(30deg);
}
div.rotateY2 {
-webkit-transform: perspective(400) rotateY(40deg);
}
div.rotateY3 {
-webkit-transform: perspective(100) rotateY(40deg);
}
div.preserve3d1 {
-webkit-transform: perspective(200) rotateY(40deg);
}
div.preserve3d2 {
-webkit-transform: perspective(200) rotateY(40deg);
-webkit-transform-style: preserve-3d;
}
div.box3d div {
-webkit-transform: translateZ(70px);
}
|
There are several transform functions that can be defined for an element. You can use a single function or a list of functions. Each allows you to adjust a specific component of the transform, such as scale or rotation. Adjusting the transform component by component is convenient for the developer.
However, internally, the browsers manage the whole transform with the matrix
transform. In fact, when you query the current transform of an element with
window.getComputedStyle(elem).webkitTransform;, you will
get a long string such as
matrix(-1.8301, -0.7378, -0.0652, -2.0699, 7.6405, 43.3315).
The browser provides you its internal matrix transform, even though you didn't
initially define a matrix transform. This is because a matrix is mathematically
the most compact way to define a complex transform with multiple
components.
Since a matrix transform can be difficult to manipulate, there is a
helper mechanism: What you usually want to do is to manipulate the transform
component by component, and not directly via matrix. This can be done with a
JavaScript helper class called WebKitCSSMatrix.
WebKitCSSMatrix JavaScript helper class can be used like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // get existing transform
var t = window.getComputedStyle(elem).webkitTransform;
// create a helper class
var matrix = new WebKitCSSMatrix(t);
// manipulate the matrix with its methods:
var t2 = matrix.rotate(10); // degrees
var t2 = matrix.scale(2.0, 2.0);
var t2 = matrix.translate(10, 10);
var t2 = matrix.multiply(othermatrix);
// finally, assign the new transform to an element
elem.style.webkitTransform = t2;
|
The example below demonstrates the use of WebKitCSSMatrix. The box has a
complex transform that is defined component by component. On each click of the
box, the box is rotated by 10 degrees. The current transform is displayed on
the right, in the form provided by the browser.
1 2 3 4 | <div id="boxmatrix">
complex transform here
</div>
<p id="debug7">debug message here</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* transformed box */
div#boxmatrix {
position: absolute;
left: 20%; top: 20%;
width: 30%; height: 30%;
color: white; background: green;
border: 2px solid white;
/* a complex transform */
-webkit-transform: rotate(-10deg) scale(1.7, 2.2) skewY(17deg) translateY(20px);
}
/* for displaying debug info */
p#debug7 {
float: right;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // get handles to DOM objects
var mybox = document.getElementById("boxmatrix");
var debug = document.getElementById("debug7");
mybox.innerHTML = "Click me!";
// set click listener
mybox.addEventListener('click', function(e) {
// read current transform of box
var t = window.getComputedStyle(this).webkitTransform;
// rotate box by 10 degrees
var matrix = new WebKitCSSMatrix(t);
t = matrix.rotate(10);
// assign new matrix
this.style.webkitTransform = t;
// display new transform matrix
debug.innerHTML = t;
}, false);
|
The previous examples in this guide were intentionally simple for learning purposes. This chapter will demonstrate some real life applications, where CSS effects are valuable. The demos here are supposed to be self-contained and the code can be copy-pasted into real applications.
The examples are designed for mobile browsers, and have been fitted into a mobile screen frame. However, all cases can be adapted to the desktop screen as well.
This example demonstrates an accordion widget that is typically used for displaying data that does not fit into a screen all at once. Accordion has data sections that can be expanded and contracted by the user.
The accordion in this demo has sections that begin with an h1-element, which specifies the title of the section and is also clickable. A div-element after the h1 has all the content of the section, and can have any element structure underneath.
When the h1-title is clicked, the section under it slides open and close. The
sliding is done with a CSS transition that manipulates the height CSS
property of the div-element. The opacity is manipulated too.
In the JavaScript code, the initial heights of the sections
are stored in h1 element itself in a custom attribute named "data". By using a
custom attribute, it is not necessary to create an array variable in JavaScript.
Also, later in the click handler the attribute is readily accessible.
(Note that the HTML5 specification will officially allow custom "data-"
attributes in elements.)
When the initial heights are stored in h1 attributes, all the sections are closed by setting their height to zero.
A click handler is finally attached to each h1. In the click handler, a class
named "opened" is used to track the state of the section: opened or closed.
If currently open, the height of the div is transitioned to zero, and if
currently closed, the height of the div is transitioned to the initial height
that was stored in a h1 attribute in the beginning of the code.
Even with a simple technique like this, the user experience can be improved significantly on a small screen.
To start the demo, apply both CSS and JavaScript. Go ahead and tweak CSS and JavaScript, reapply them, and see the effect right here in your browser!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <p class="title" id="caseaccordion">Accordion</p>
<h1>News</h1>
<div>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
<p>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi,
euismod in, pharetra a, ultricies in, diam. Sed arcu.
Cras consequat.</p>
</div>
<h1>Sport</h1>
<div>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
</div>
<h1>Finance</h1>
<div>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
</div>
<h1>Business</h1>
<div>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
<p>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec
consectetuer ligula vulputate sem tristique cursus.
Nam nulla quam, gravida non, commodo a,
sodales sit amet, nisi.</p>
</div>
</ul>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase p {
margin: 8px 0;
font-size: 80%;
}
div.usecase h1 {
color: #aaa;
padding: 12px;
font-size: 110%;
border: none;
border-bottom: 2px solid #fff;
cursor: pointer;
padding-left: 43px;
background: #555 url(feed.png) no-repeat 5px 5px;
}
div.usecase h1+div {
padding: 0 20px;
-webkit-transition: height 0.5s, opacity 0.5s;
overflow: hidden;
}
div.usecase h1.opened {
color: #fff;
background-color: #aaa;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // get handle to outermost div
var mybox = document.getElementById("caseaccordion").parentNode;
// fetch elements into arrays
var h1list = mybox.getElementsByTagName("h1");
var divlist = mybox.getElementsByTagName("div");
// read and store original heights
for(var i=0; i<divlist.length; i++) {
var div = divlist[i];
var he = getPos(div).height;
h1list[i].setAttribute("data", he);
div.style.height = 0;
}
// attach click handlers
for(var i=0; i<h1list.length; i++) {
var h1 = h1list[i];
h1.addEventListener('click', function(e) {
var div = nextelem(this);
if (hasClass(this, "opened")) {
removeClass(this, "opened");
div.style.height = 0;
div.style.opacity = 0;
} else {
addClass(this, "opened");
var origh = this.getAttribute("data");
div.style.height = origh+"px";
div.style.opacity = 1;
}
}, false);
}
|
This example demonstrates using CSS to implement an image-viewing application.
First the screen shows thumbnails of all images. Upon image click, the image is enlarged to fit the whole screen. Second click shrinks the image back to thumbnail. Again, the visual zoom effect is done with a CSS transition.
There is one hidden extra img-element in the page that takes part in the zooming
effect. The original images are not manipulated, they are left where they
are. On click, the extra img-element is first positioned right on top of the
clicked image, and then displayed. This position is also saved into JavaScript
variables. Then after CSS flush, a CSS transition is used to animate CSS
properties left, top and width. The extra image zooms in and fills the whole
screen horizontally.
When the zoomed extra image is clicked, the process is reversed: the CSS
properties left, top and width are set back to their original saved values.
And when the transition ends, the webkitTransitionEnd-event is
fired, the extra image is hidden again.
For additional details, see the comments in the code.
To start the demo, apply both CSS and JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <p class="title" id="casezoom">Image zoom</p>
<div id="piccontainer">
<img src="1.png" />
<img src="2.png" />
<img src="3.png" />
<img src="2.png" />
<img src="3.png" />
<img src="1.png" />
<img src="3.png" />
</div>
<!-- img that does the zoom effect, initially hidden -->
<img id="zooming" src="3.png" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase img {
width: 24%;
padding: 5px;
margin: 2px;
border: 1px solid #bbb;
background: #ddd;
position: relative;
cursor: pointer;
}
div.usecase img:hover {
border: 1px solid #888;
background: #fff;
}
div.usecase img#zooming {
position: absolute;
left: 0; top: 0;
margin: 0;
display: none;
}
div.usecase img.zoomactive {
-webkit-transition: left 0.5s, top 0.5s, width 0.5s;
}
div.usecase div.fade {
opacity: 0.5;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | // get handle to outermost div
var mybox = document.getElementById("casezoom").parentNode;
var imglist = mybox.getElementsByTagName("img");
var flo = document.getElementById("zooming");
var container = document.getElementById("piccontainer");
// remember original values
var origLeft = 0;
var origTop = 0;
var origWidth = 0;
// attach click handlers
for(var i=0; i<imglist.length; i++) {
var img = imglist[i];
img.addEventListener('click', function(e) {
if (this.id == "zooming") {
// clicked on a zoomed image, unzoom it
// hint for webkitTransitionEnd-handler: do your stuff!
addClass(flo, "hidenext");
// transition to original pic
flo.style.left = origLeft;
flo.style.top = origTop;
flo.style.width = "24%";
// webkitTransitionEnd will fire soon...
}
if (!hasClass(container, "fade")) {
// image clicked, zoom it
// fade all the pictures a little
addClass(container, "fade");
// move the zooming image into the same location
// as the clicked image
origLeft = (this.offsetLeft-this.style.marginLeft)+"px";
origTop = (this.offsetTop-this.style.marginTop)+"px";
origWidth = (this.offsetWidth-12)+"px";
flo.style.left = origLeft;
flo.style.top = origTop;
this.style.width = origWidth;
show(flo);
// use the same image as clicked on
flo.setAttribute("src", this.getAttribute("src"));
// flush CSS and then start zoom transition from a timeout
setTimeout(function() {
addClass(flo, "zoomactive");
flo.style.left = 0;
flo.style.top = "120px";
flo.style.width = "95%";
}, 0);
}
}, false);
}
// attach transition end handler
flo.addEventListener('webkitTransitionEnd', function(e) {
if (hasClass(flo, "hidenext")) {
// we are back at original location, hide the extra image
hide(flo);
removeClass(flo, "zoomactive");
removeClass(flo, "hidenext");
// show all images again, remove fading
removeClass(container, "fade");
}
}, false);
|
This example demonstrates sliding parts of HTML left to right and back. This effect is quite versatile, and could be used for the manipulation of application settings, as demonstrated here. The actual settings of the items in this demo are left as an exercise to the reader.
The HTML shows five "pages" that will alter in the view. Only one page is displayed at a time, all other pages are hidden. A slide effect transitions one page to another.
When an anchor in the main screen is clicked, the main page slides out of the view
to the left and the target page slides in to fill the screen. The target
page is identified by href of the anchor. Appearing back button at the top
of the screen reverses the slide effect, and moves the pages to the right
again, bringing the main screen back.
The slide effect is achieved by transitioning CSS property left for
absolute positioned pages.
The demo also supports sliding the pages left and right with finger swipes on Starlight browser. See the bottom of the JavaScript for the touch handler code.
To start the demo, apply both CSS and JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <p class="title" id="casesliding">Sliding screens</p>
<div id="page_main">
<ul>
<li><a href="page_display">Display</a></li>
<li><a href="page_wifi">Wifi</a></li>
<li><a href="page_security">Security</a></li>
<li><a href="page_about">About</a></li>
</ul>
</div>
<div id="page_display">
<ul>
<li>Lines: <span>20</span></li>
<li>Height: <span>400px</span></li>
<li>Portait: <span>Yes</span></li>
</ul>
<p>Note: something here.</p>
</div>
<div id="page_wifi">
<ul>
<li>Wifi: <span>On</span></li>
<li>Threshold: <span>5.4</span></li>
</ul>
<p>Note: Wifi must first be turned on in system
settings.</p>
</div>
<div id="page_security">
<ul>
<li>Type: <span>WPA2</span></li>
<li>Password: <span>***</span></li>
</ul>
</div>
<div id="page_about">
<h3>About</h3>
<p>This mobile application was authored by me.</p>
<h3>Feedback</h3>
<p>Please send any feedback to xyz@internet.buzz</p>
</div>
<div id="back">
<div>←</div>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase {
font-family: Arial;
}
div.usecase li {
border-bottom: 2px solid #222;
background: #888;
padding: 4px;
list-style: none;
color: #333;
}
div.usecase li span {
color: #ddd;
}
div.usecase > div a {
text-decoration: none;
color: #fff;
display: block;
}
div.usecase > div p {
font-size: 60%;
padding: 10px;
color: #aaa;
}
div.usecase > div {
display: none;
position: absolute;
width: 100%;
height: 100%;
font-size: 200%;
}
div.usecase > div#page_main {
display: block;
}
div.usecase > div.transit {
-webkit-transition: left 0.4s;
}
div.usecase > div#page_main li {
background: #888 url(right.png) no-repeat right 4px;
}
div.usecase > div#back {
position: absolute;
width: 100%;
top: 0;
height: 35px;
background: #333;
text-align: center;
color: #aaa;
cursor: pointer;
}
div.usecase > div.showback {
display: block;
}
div.usecase > div#page_about {
-webkit-border-radius: 10px;
border: 5px solid #aaa;
background: #eee;
width: 96%;
height: 80%;
font-size: 130%;
}
div.usecase > div#page_about * {
padding: 5px;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | // get handle to outermost div
var mybox = document.getElementById("casesliding").parentNode;
var divlist = mybox.getElementsByTagName("div");
var anchors = mybox.getElementsByTagName("a");
var backelem = document.getElementById("back");
var prevpage = null;
var curpage;
// func to slide pages left
var slide2Left = function(e) {
var nextpage = e.target.getAttribute("href");
var next = document.getElementById(nextpage);
prevpage = parentElem(e.target, "div");
next.style.left = "100%";
show(next);
curpage = next;
setTimeout(function() {
addClass(next, "transit");
addClass(prevpage, "transit");
addClass(backelem, "showback");
prevpage.style.left = "-100%";
next.style.left = "0";
}, 0);
e.preventDefault();
};
// func to slide pages right
var slide2Right = function(e) {
addClass(prevpage, "transit");
addClass(curpage, "transit");
removeClass(backelem, "showback");
curpage.style.left = "100%";
prevpage.style.left = "0";
e.preventDefault();
};
// add operations for main page anchors
for(var i=0; i<anchors.length; i++) {
var anc = anchors[i];
anc.addEventListener('click', function(e) {
slide2Left(e);
});
}
// add operations for sub page divs
for(var i=0; i<divlist.length; i++) {
var div = divlist[i];
div.addEventListener('webkitTransitionEnd', function(e) {
removeClass(this, "transit");
}, false);
}
// backelem slides back too
backelem.addEventListener('click', function(e) {
slide2Right();
}, false);
// additional handlers for touch sweeps
var sliding = false;
for(var i=0; i<anchors.length; i++) {
var anc = anchors[i];
anc.addEventListener('manipulatestart', function(e) {
e.preventDefault();
});
anc.addEventListener('manipulatemove', function(e) {
e.preventDefault();
if (e.panX < -30 && !sliding) {
sliding = true;
slide2Left(e);
}
});
anc.addEventListener('manipulateend', function(e) {
e.preventDefault();
sliding = false;
});
}
for(var i=0; i<divlist.length; i++) {
var div = divlist[i];
div.addEventListener('manipulatestart', function(e) {
e.preventDefault();
});
div.addEventListener('manipulatemove', function(e) {
e.preventDefault();
if (e.panX > 30 && !sliding) {
sliding = true;
slide2Right(e);
}
});
div.addEventListener('manipulateend', function(e) {
e.preventDefault();
sliding = false;
});
}
|
This example demonstrates how an element can be turned around in 3D space like a regular paper page with different content on the front and back. A 3D CSS transform with a perspective, plus a CSS transition is used to achieve the effect.
The two sides of the page are laid out in HTML surrounded by two div.pages.
div#p1 acts as the front side and the div#p2 as the back side.
Initially both are hidden. When JavaScript is run, div#p1 gets
displayed and click handlers are registered to both divs.
When clicking the page, both pages are turned 90 degrees so that the side of the
pages are facing the user. Both pages thus become invisible. At this point,
webkitTransitionEnd is fired, and in the handler code the previous front
page gets hidden and the back page gets displayed. Then the back page is turned another 90 degrees to complete the flip.
The example also works to demonstrate how it is possible to handle only the
first webkitTransitionEnd event and no more: the event listener is removed
from the element with a removeEventListener function call inside the event
handler routine transEndHandler.
The demo also turns page on left sweep gesture on Starlight browser. See the touch related code at the bottom of JavaScript.
To start the demo, apply both CSS and JavaScript.
Try experimenting with different values of perspective, and remove it altogether. You might also try to flip the page around its X-axis.
Note: Perspective and transform-style don't work with version 1.0 of Starlight browser. To see the correct results, try WebKit nightly builds on a Mac.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <p class="title">3D page flip</p>
<div id="p1" class="page">
<h2>Page 1</h2>
<p>Hello there, this is page one of the demo.</p>
<p>Click to turn the page.</p>
<img class="arrow" src="right.png" />
</div>
<div id="p2" class="page">
<h2>Page 2</h2>
<p>This is page two.</p>
<img class="pic" src="1.png" />
<p>This is a regular web page and
can be scrolled down. </p>
<img class="pic" src="2.png" />
<p>Now click again to go back to page 1.</p>
</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase h2 {
color: #777;
}
div.page {
-webkit-transition: -webkit-transform 0.6s;
border: 4px solid #444;
-webkit-border-radius: 10px;
width: 75%;
height: 60%;
background: #ddd;
padding: 5%;
margin: 5%;
display: none;
/* enable a scrollbar */
overflow-y: auto;
/* for right arrow below */
position: relative;
}
div.page img.arrow {
position: absolute;
right: 0; bottom: 10px;
}
div.page img.pic {
width: 90%;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | var p1 = document.getElementById("p1");
var p2 = document.getElementById("p2");
// page click handler for both pages
var clickHandler = function(e) {
var targ = this;
turnPage(targ, targ==p1 ? p2:p1);
}
p1.addEventListener('click', clickHandler, false);
p2.addEventListener('click', clickHandler, false);
// show page 1
show(p1);
// turns page p1 away and reveals p2
function turnPage(p1, p2) {
// common perspective
var perspec = "perspective(500) ";
// transition end handler
var transEndHandler = function(e) {
p1.removeEventListener("webkitTransitionEnd", transEndHandler, false);
hide(p1);
show(p2);
// continue rotating the back side
setTimeout(function(){
p2.style.webkitTransform = perspec+"rotateY(0deg)";
}, 10);
}
// call above handler when transition complete
p1.addEventListener("webkitTransitionEnd", transEndHandler, false);
// now rotate both pages 90 degrees
p2.style.webkitTransform = "rotateY(90deg)";
p1.style.webkitTransform = perspec+"rotateY(-90deg)";
}
// flip routines for touch sweep left
var sliding = false;
p1.addEventListener('manipulatemove', function(e) {
if (e.panX < -30 && !sliding) {
sliding = true;
clickHandler(e);
}
});
p1.addEventListener('manipulateend', function(e) {
sliding = false;
});
p2.addEventListener('manipulatemove', function(e) {
if (e.panX < -30 && !sliding) {
sliding = true;
clickHandler(e);
}
});
p2.addEventListener('manipulateend', function(e) {
sliding = false;
});
|
In this example, with a very few lines of JavaScript code (ignoring the extra touch code at the bottom), three images are animated to the screen, one after the other. Try the demo and advance to the next image by clicking the "Next" link.
There are no element styling declarations in the code; JavaScript just orchestrates the behavior of the app, and all the styling is done in the CSS. No hard-coded limitations exist in the code (e.g. number of images).
The power of this demo lies in the CSS: a CSS animation named mycoolanim is
declared and applied for img-elements. The whole animation takes 50 seconds
and has 3 keyframes defined at 0%, 1% and 100%.
The 0% keyframe defines the target (an img-element) to be invisible, enlarged and outside the screen. The 1% keyframe defines the target to be in the middle of the screen at normal size, slightly rotated. The 100% keyframe defines the target to be enlarged, rotated to another direction and slightly invisible again.
Now, 1% of 50s is half a second. The CSS animation animates the image from off the screen to the middle of the screen, and then slowly animates the image off the screen again during the rest of the animation, 49.5s. This simple technique creates a compelling effect.
Try catching the webkitAnimationEnd event and animate the next image to the
screen automatically.
At the bottom is extra JavaScript code for touch on Starlight. The animation stops on finger press, and the image can be moved around and be sent away with a flick gesture. Another image appears after the finger is lifted up.
1 2 3 4 5 6 7 | <p class="title" id="caseslide">Image slide show</p>
<img src="1.png" />
<img src="2.png" />
<img src="3.png" />
<a href="#">Next</a>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase img {
position: absolute;
left: -100%; top: 150%;
width: 20%;
padding: 10px;
background: #222;
}
div.usecase a {
float: right;
font-size: 200%;
/* stay on top of image */
position: relative;
}
div.usecase img.anim {
-webkit-animation-name: 'mycoolanim';
-webkit-animation-duration: 50s;
}
/* keyframes for my animation */
@-webkit-keyframes 'mycoolanim' {
0% { opacity: 0;
-webkit-transform: rotate(-20deg) scale(8); }
/* a trick here: bring image fast to screen, and then slowly animate
away */
1% { left: 10%; top: 30%; width: 90%;
-webkit-transform: rotate(20deg) scale(1);
}
100% { left: 10%; top: 30%; width: 90%; opacity: 0.3;
-webkit-transform: rotate(-30deg) scale(5);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | // get handle to outermost div
var mybox = document.getElementById("caseslide").parentNode;
// get images and their count
var imglist = mybox.getElementsByTagName("img");
var imagecount = imglist.length;
// get first anchor-element, "Next":
var a = mybox.getElementsByTagName("a")[0];
// attach click handler to anchor
a.addEventListener('click', function(e) {
animateNextImg();
e.preventDefault();
}, false);
// animate next img to the screen
var picnum = 0; // counter for selecting images
function animateNextImg() {
// pick images one after another
var img_prev = imglist[(picnum++)%imagecount];
var img = imglist[(picnum)%imagecount];
// remove anim from previous image
removeClass(img_prev, "anim");
// add anim to the next img
addClass(img, "anim");
}
// extra touch routines below
// add manipulation routines for images
for(var i=0; i<imglist.length; i++) {
var img = imglist[i];
img.addEventListener('manipulatestart', function(e) {
e.preventDefault();
// stop anim and freeze the current form
var elem = e.target;
elem.oldPos = getPos(elem);
freezeState(elem);
});
img.addEventListener('manipulatemove', function(e) {
e.preventDefault();
var elem = e.target;
elem.style.left = elem.oldPos.left + e.panX + "px";
elem.style.top = elem.oldPos.top + e.panY + "px";
});
img.addEventListener('manipulateend', function(e) {
e.preventDefault();
var elem = e.target;
elem.style.left = "-100%";
elem.style.width = "10%";
// animate next img
setTimeout(animateNextImg, 200);
});
}
// freeze the current transform state of elem
function freezeState(elem) {
var computedStyle = document.defaultView.getComputedStyle(elem, null);
var l = parseInt(computedStyle.getPropertyValue("left"));
var t = parseInt(computedStyle.getPropertyValue("top"));
var w = parseInt(computedStyle.getPropertyValue("width"));
var tr = computedStyle.webkitTransform;
removeClass(elem, "anim");
elem.style.left = l+"px";
elem.style.top = t+"px";
elem.style.width = w+"px";
elem.style.webkitTransform = tr;
}
|
This example demonstrates how an icon grid, a traditional UI pattern in mobile phones, could be implemented as a web application that is utilizing CSS effects effectively. Nice look-and-feel can be achieved with relatively little amount of code, a testimony to the power of web.
On the screen, icons are laid out in a grid using absolute CSS positioning. Instead of using touch, this example simulates navigation with a keypad. On the screen you have two anchors, "Left" and "Right", that simulate the keypad left and right keys that move the focus around the grid.
The implementation is quite elaborate, as it utilizes both CSS
transitions and CSS animations at the same time. CSS transition is used to
control the scale and opacity of an icon and CSS animation is used to flip the
active icon around in its place. CSS transition starts right away, but the CSS
animation is delayed by 0.5s. Take a look at the img.active class for
details.
Again, like in the Image slide show example above, the implementation is modular in the sense that it keeps the styling issues in CSS, and behavioral aspects in JavaScript. JavaScript controls the use of CSS effects just by adding and removing necessary class names of HTML elements.
Apply both CSS and JavaScript and start playing with the example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <p class="title" id="casegrid">Icon grid</p>
<!-- the gray box behind the icon -->
<div id="graybox"></div>
<img src="feed.png" />
<img src="feed.png" />
<img src="feed.png" />
<img src="feed.png" />
<img src="feed.png" />
<img src="feed.png" />
<img src="feed.png" />
<a href="#" class="left">Left</a>
<a href="#" class="right">Right</a>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
}
div.usecase a.left, div.usecase a.right {
float: left;
font-size: 150%;
}
div.usecase a.right {
float: right;
}
/* the gray border behind the icon */
div.usecase div {
position: absolute;
background: #ddd;
width: 30%; height: 20%;
-webkit-border-radius: 15px;
-webkit-transition: left 0.3s, top 0.3s;
}
div.usecase img {
position: absolute;
width: 30%;
-webkit-transition: -webkit-transform 0.5s, opacity 0.5s;
-webkit-transform: scale(0.4);
opacity: 0.5;
}
/* class that specifies the active CSS effect */
div.usecase img.active {
/* transition these two properties */
-webkit-transform: scale(0.8);
opacity: 1;
/* and start an anim in 0.5secs */
-webkit-animation-name: 'rotateanim';
-webkit-animation-duration: 0.5s;
-webkit-animation-delay: 0.5s;
-webkit-animation-timing-function: linear;
}
/* keyframes for my animation */
@-webkit-keyframes 'rotateanim' {
0% { -webkit-transform: rotateY(0deg) scale(0.8); }
100% { -webkit-transform: rotateY(360deg) scale(0.8); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | // get handle to outermost div
var mybox = document.getElementById("casegrid").parentNode;
// get images and their count
var imglist = mybox.getElementsByTagName("img");
var imagecount = imglist.length;
// get both anchor-elements
var a = mybox.getElementsByTagName("a");
var graybox = document.getElementById("graybox");
// register click handlers to anchors
a[0].addEventListener('click', function(e) {
moveCursor(-1);
e.preventDefault();
}, false);
a[1].addEventListener('click', function(e) {
moveCursor(1);
e.preventDefault();
}, false);
// cursor position (0<= x <imagecount)
var pos = 0;
var oldpos = -1;
function moveCursor(direction) {
// calculate next pos (0<= x <imagecount)
pos = (pos+direction)%imagecount;
if (pos < 0)
pos = imagecount-1;
// move graybox
graybox.style.left = leftCoord(pos);
graybox.style.top = topCoord(pos);
// remove anim from previous icon
if (oldpos >= 0)
removeClass(imglist[oldpos], "active");
// add anim to next icon
addClass(imglist[pos], "active");
// remember this position
oldpos = pos;
}
// scatter icons evenly in the screen on startup
function scatterIcons() {
for(var i = 0; i<imagecount; i++) {
var img = imglist[i];
img.style.left = leftCoord(i);
img.style.top = topCoord(i);
}
}
// calculate left and right coordinates for given img position
function leftCoord(pos) {
return (pos%3*30)+5+"%";
}
function topCoord(pos) {
return 70+(parseInt(pos/3)*70)+"px";
}
// initialize app
scatterIcons();
moveCursor(0);
|
This example demonstrates how a collection of images can be viewed and manipulated on the screen with multiple fingers. One finger can pan images, and two fingers can rotate, pinch and zoom images.
The images are transformed on the screen by modifying the matrix of
webkitTransform CSS property. The new rotation and scale values
provided by the manipulatemove event are fed into the new transform
matrix.
Note that the positions of the images are set with traditional CSS left
and top CSS properties instead of using the transform function
translate(). The reason for this is that left and top represent
coordinates in the parent's coordinate space, as do pan coordinates of
the manipulatemove event. The coordinates of translate() are
"twisted" by the transform matrix, and not suitable for tracking the
finger.
No CSS transitions or CSS animations are used here.
A double click on an image resets the image transform and brings the image back to the center of the screen.
This example requires the Starlight browser since it relies on touch manipulation events provided by the Starlight.
1 2 3 4 5 | <p class="title" id="casemanip">Image manipulation</p>
<img src="1.png" />
<img src="2.png" />
<img src="3.png" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | div.usecase p.title {
color: #fff; background: #555;
margin: 0; padding: 5px;
font-size: 110%;
border-bottom: 3px solid #333;
/* do not select text while dragging */
-webkit-user-select: none;
}
div.usecase img {
position: absolute;
width: 40%;
/* do not select img while dragging */
-webkit-user-select: none;
margin: 2px; padding: 5px;
border: 1px solid #bbb;
background: #ddd;
opacity: 0.8;
}
/* class to present an active manipulation */
div.usecase img.manip {
opacity: 1;
border: 1px solid #777;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | // get handle to outermost div
var mybox = document.getElementById("casemanip").parentNode;
// get images and their count
var imglist = mybox.getElementsByTagName("img");
var imagecount = imglist.length;
// register manipulate listeners
for (var i = 0; i < imagecount; i++) {
var img = imglist[i];
// dblclick sets img in center of screen
img.addEventListener('dblclick', function(e) {
bringFront(this);
// set some sensible position
this.style.left = "20px";
this.style.top = "120px";
}, false);
img.addEventListener('manipulatestart', function(e) {
e.preventDefault();
bringFront(this);
addClass(this, "manip");
// remember starting position (x,y)
this.oldPos = getPos(this);
// remember starting transform
var t = getComputedStyle(this).webkitTransform;
this.oldMatrix = new WebKitCSSMatrix(t);
}, false);
img.addEventListener('manipulatemove', function(e) {
e.preventDefault();
// set new transform and position
transform(this, e);
}, false);
img.addEventListener('manipulateend', function(e) {
e.preventDefault();
removeClass(this, "manip");
}, false);
}
// transform the img
function transform(img, e) {
// calculate and set new matrix
var m = img.oldMatrix.rotate(0,0,e.rotation*360/2/3.1415).scale(e.scale);
img.style.webkitTransform = m;
// set position too
img.style.left = img.oldPos.left + e.panX + "px";
img.style.top = img.oldPos.top + e.panY + "px";
}
// scatter images in the screen
function scatterImages() {
for(var i = 0; i<imagecount; i++) {
var img = imglist[i];
var x = i*40 + 5;
var y = i*70 + 50;
img.style.left = x+"px";
img.style.top = y+"px";
}
}
// bring img in front of other
function bringFront(img) {
for(var i = 0; i<imagecount; i++)
imglist[i].style.zIndex = 1;
img.style.zIndex = 2;
}
scatterImages();
|
Here are external links to the relevant standardization work documents for the technologies being demonstrated in this guide.
The examples of this guide use some utility functions listed below. You may experiment using these functions in all of the examples above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | // useful generic utility functions; no dependency to any javascript
// framework
// show and hide an elem
function show(elem) {
elem.style.display = 'block';
}
function hide(elem) {
elem.style.display = 'none';
}
// adds a class to an element
function addClass(elem, className) {
if (!hasClass(elem, className)) {
var cl = ""+elem.className;
elem.className += (cl.length>0 ? " " : "") + className;
}
}
// removes a class from an element
function removeClass(elem, className) {
if (hasClass(elem, className)) {
var reg = new RegExp('(\\s|^)'+className+'(\\s|$)');
elem.className = elem.className.replace(reg, ' ');
}
}
// does elem have a class?
function hasClass(elem, className) {
var reg = new RegExp("(^|\\s)" + className + "(\\s|$)");
return reg.test(elem.className);
}
// get next elem
function nextelem(elem) {
var sib = elem.nextSibling;
while (sib && sib.nodeType != 1)
sib = sib.nextSibling;
return sib;
}
// get previous elem
function prevelem(elem) {
var sib = elem.previousSibling;
while (sib && sib.nodeType != 1)
sib = sib.previousSibling;
return sib;
}
// finds an element of desired type above
function parentElem(elem, tagtype) {
var p = elem.parentNode;
tagtype = tagtype.toUpperCase();
while (p && p.nodeName != tagtype)
p = p.parentNode;
return p;
}
// returns position of element, CSS left and top
function getPos(elem) {
var o = {};
var computedStyle = document.defaultView.getComputedStyle(elem, null);
o.left = parseInt(computedStyle.getPropertyValue("left"));
o.top = parseInt(computedStyle.getPropertyValue("top"));
o.height = parseInt(computedStyle.getPropertyValue("height"));
o.width = parseInt(computedStyle.getPropertyValue("width"));
return o;
}
|
Note that if you heavily modify several of the examples above, they examples may interfere with each other as they execute in the same namespace.
All the examples run without an external JavaScript framework. However, the guide internally uses the jquery javascript framework to power the dynamic parts of the guide. Source listings are syntax colored with pygments python library.
Here is the changelog of this document:
Oct 14, 2009 - Version 1.0