kodlab - KOnstantin Denerz LAB
kodlab

Animierter Kaffeebecher (CSS)

CSS Animation?

CSS bietet Style-Animationen an. Dabei wird der Übergang von einem Style zu einem anderen Style animiert. Die Definition dieser Animationen besteht aus 2 Komponenten:

  • dem Style, der die Animation beschreibt und
  • den Keyframes, die den Start, das Ende und die Zwischenschritte deklarieren

Beispiel - Kaffeedampf

Als nächstes zeige ich eine Beispiel-Animation anhand eines Dampfes, der aus einem Kaffeebecher kommt.

Um die Redundanz im Quellcode gering zu halten wird der CSS-Code nur für Webkit-Browser angezeigt. Den gesamten Quellcode findet man hier: CoffeeCup-CSS

Das Ergebnis

Das Ergebnis besteht aus einem Hintergrundbild mit Kaffeebecher und 3 unterschiedlichen Dampfbildern. Die Dampfbilder bewegen sich zeitversetzt auf der Y-Achse von unten nach oben. Zusätzlich wird beim Start und Ende der Animation die Transparenz runtergeschraubt, damit ein FadeIn- und ein FadeOut-Effekt entsteht.


Keine Animation zu sehen? Dann probiere einen dieser Browser aus:

Design in Inkscape

Den Kaffebecher (bis auf die Holz- / Kreidetafeltexturen) und den Dampf habe ich komplett in Inkscape realisiert. Der Dampf war etwas schwierig zu zeichnen, da Inkscape ein Vektorzeichentool ist und (fast) keine Pixelverarbeitung wie bei Photoshop oder GIMP unterstützt. Dabei musste ich viel mit Transparenz und Blur-Filter arbeiten. Hier steckt auch etwas Verbesserungspotenzial drin, welches man durch die Verwendung von Stockphotos vom Dampf ausnutzen kann. Die Stockfotos kriegt man zum Beispiel hier: iStockphoto

Den Sourcecode findet man hier: CoffeeCup-SVG

Struktur

Die Struktur ist ziemlich einfach. Das äußere div-Element definiert den gesamten Kontainer mit dem Hintergrund (Kaffeetasse). Die inneren div-Elemente repräsentieren den Dampf.

HTML
1
2
3
4
5
<div class="coffee-cup">
  <div class="coffee-steam-1"></div>
  <div class="coffee-steam-2"></div>
  <div class="coffee-steam-3"></div>
</div>

Style

Wenn man sich den CSS-Code anschaut, dann sieht man zuerst die Keyframes, die den Style pro Schritt angeben.

CSS3 (Webkit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* animation definition */
@-webkit-keyframes coffee-steam-animation {
  from {
      opacity:0;
      -webkit-transform: translatey(30px);
  }
  50% {
      opacity:1;
      -webkit-transform: translatey(-10px);
  }
  to {
      opacity:0.2;
      -webkit-transform: translatey(-40px);
  }
}

In diesem Block wird die Animation coffee-steam-animation den Dampf-divs zugewiesen. Danach muss man nur noch die Zeitverzögerung definieren.

CSS3 (Webkit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* assign animation to each div element*/
.coffee-cup div{
  -webkit-animation-name: coffee-steam-animation;
  -webkit-animation-iteration-count: infinite;
  -webkit-animation-duration: 3s;
}

.coffee-steam-1{
  background: url(/images/tutorial/cssanimation/coffee-steam-1.png);
  animation-delay:0s;
  -webkit-animation-delay:0s;
}

.coffee-steam-2{
  background: url(/images/tutorial/cssanimation/coffee-steam-2.png);
  animation-delay:1.5s;
  -webkit-animation-delay:1.5s;
}

.coffee-steam-3{
  background: url(/images/tutorial/cssanimation/coffee-steam-3.png);
  animation-delay:0.5s;
  -webkit-animation-delay:0.5s;
}

Hier der Basisstyle:

CSS3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.coffee-cup{
  position:relative;
  width:400px;
  height:500px;
  background:url("/images/tutorial/cssanimation/coffee-cup.png") 
  no-repeat center bottom;
}

.coffee-cup div{
  width:200px;
  height:200px;
  left:100px;
  position:absolute;
  background-repeat:no-repeat;
  background-position:center center;
}

Fazit

Die Verwendung der CSS-Animationen ist eine tolle Sache, da die Animationen nativ, deklarativ und leicht zu definieren sind. Leider werden die CSS-Animationen im IE erst ab der Version 10 unterstützt. Für die älteren Browserversionen bleibt einem nur der Einsatz von JavaScript. Dazu kann man zum Beispiel jQuery - Animate verwenden.


Fault Tolerance

it’s not a database

Source:
@jrecursive
http://webapptoolkit.com/fault-tolerance_the-cloud.html

Globale Fehlerbehandlung in JavaScript

Hier ist ein kleines Beispiel für das “globale” Abfangen von unbehandelten Fehlern in JavaScript. Das Abfangen von Fehler in JavaScript passiert über folgenden technischen Code:

JavaScript
1
2
3
4
5
try {
  // business logic
} catch(exception) {
  // error handling
}

Dies hat zufolge, dass man an vielen Stellen den “technischen” Code der Fehlerbehandlung dupliziert, um unerwartete Fehler abzufangen. Jetzt könnte man sich vorstellen ein Try-Catch über die gesamte Logik an einer Stelle zu definieren und somit alle Fehler innerhalb dieses Blocks abzufangen. Dies würde auch funktionieren wenn man nur einen Einstiegspunkt für die Logik hat.

Oft ist es aber so, dass es mehrere Einstiegspunkte für Logikaufrufe gibt. Man stellt sich vor es gibt mehrere Module, die dynamisch mit entsprechenden Modularisierung-Frameworks eingebunden werden. An dieser Stelle müsste die Fehlerbehandlung für jedes Modul einzeln an mehreren Stellen definiert werden.

Um die Doppelung und die Vermischung der Geschäftslogik mit dem technischen Code zu vermeiden, bietet sich die Verwendung eines dynamischen Stellvertreters (Proxy) an.

Das untere Beispiel soll einen möglichen Ansatz für eine zentrale Fehlerbehandlung zeigen.

In der HTML-Seite ist der Anwendungskontext beschrieben. Es gibt 2 Workflows, die eine Liste mit Tasks bekommen. Die Ausführung des zweiten Tasks führt bei beiden Workflows zu einem Fehler, da die Funktion bubu() nicht definiert ist.

HTML&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
<html>
  <head>
      <title>Global error handling with 
      dynamic proxies in JavaScript</title>
      <meta name="author" content="konstantin.denerz">
      <!-- third-party -->
      <script type="text/javascript" src="jquery-1.5.js"></script>
      <script type="text/javascript"
      src="mootools-core-1.3-full-compat.js"></script>
      <!-- framework -->
      <script type="text/javascript" src="util.js"></script>
      <script type="text/javascript" src="proxy-factory.js"></script>
      <script type="text/javascript" src="workflow.js"></script>
      <script type="text/javascript">
          var tasks = [
  
              {execute: function() {
  
  
  
              }},
  
              {execute: function() {
  
                  this.bubu();  // throws exception!!!
  
              }},
              {execute: function() {
              }}
          ];
  
          /* workflow with error handling */
          var workflow = specialWorkflowFactory.create();
          workflow.setTasks(tasks);
          workflow.executeFirstStep();
          
          /* workflow without error handling */
          var workflow = defaultWorkflowFactory.create();
          workflow.setTasks(tasks);
          workflow.executeFirstStep();
      </script>
  </head>
  <body></body>
</html>

Und so sehen die Strukturen aus. Neben der Workflow-Klasse gibt es 2 Factories, die Workflows mit und ohne Fehlerbehandlung erstellen. Die Fehlerbehandlung selbst gibt momentan nur den Fehler auf der entsprechenden Konsole aus.

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
47
48
49
50
51
52
53
54
55
56
57
/**
 * Contains business logic.
 */
function Workflow() {

    var _tasks = [];

    Workflow.prototype.setTasks = function setTasks(tasks) {
        _tasks = tasks;
    }

    Workflow.prototype.executeFirstStep = function executeFirstStep() {
        if ($.isArray(_tasks)) {
            $.each(_tasks, function(index, task) {
                task.execute();
            });
        }
    }

}


var defaultWorkflowFactory = {
    create : function create() {
        return new Workflow(); // default object creation
    }
}


/**
 * Should be used to handle invocations on proxies. The handler log errors. 
 * @param proxy The proxy object.
 * @param target The wrapped object.
 * @param method The called method.
 * @param args
 */
function invocationHandler(proxy, target, method, args) {
    var result = null;
    try {
        result = target[method].apply(target, args);
    } catch(e) {
        error("error occurs " + 
        e.toString(), {exception: e, errorHandlerArgs: arguments});
    }
    return result;
}


/**
 * Special factory with error handling
 */
var specialWorkflowFactory = {
    create : function create() {
    // uses proxy factory to create a proxy with error handler
        return proxyFactory.create(new Workflow(), invocationHandler); 
    }
}

Hier ist ein Beispiel für dynamische Proxies in JavaScript. Ich verwende diese, um eine Zwischenschicht über den Einstiegspunkten in die Geschäftslogik zu legen.

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
/**
 * Proxy factory.
 */
var proxyFactory = {
    /**
     * Factory method.
     * @param target Object that should be wrapped.
     * @param handler Invocation handler that should be called after the call on the proxy. 
     */
    create: function(target, handler) {
        var proxy = {};
        
        for (var method in target) {
            if ($.isFunction(target[method])) {

                function createPlaceHolder(proxy, target, method, handler) {
                    proxy[method] = function() {
                        return handler(proxy, target, method, arguments);
                    }
                }
                
                createPlaceHolder(proxy, target, method, handler);
            }
        }

        return proxy;
    }
}

Der Vollständigkeit halber noch die Hilfsfunktionen:

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
function defined(object) {
    return object !== undefined;
}

function warn(message, context) {
    log(message, context, "warn");
}

function error(message, context) {
    log(message, context, "error");
}

function debug(message, context) {
    log(message, context, "log");
}

function log(message, context, type) {

    function prefix() {
        var d = new Date();
        return [d.getFullYear() + "-" + d.getMonth() + 1 + "-" + d.getDate(), d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + "," + d.getMilliseconds()];
    }


    if (defined(window) && defined(window.console)) {
        var method;
        if ($.isFunction(window.console[type])) {
            method = type;
        } else if ($.isFunction(window.console.log)) {
            method = "log";
        }

        if (defined(method)) {
            window.console[method]([prefix().toString() + " - " + message, context]);
        }

    }
}