Creating Flying Letters

By Sergey Malenkov, November 13, 2008

This example shows how to create animation for several nodes. When you click the letters, they start transforming.

Understanding the Code

The Letters class creates a text label that starts transforming on the mouse click. The component bases most of the drawing on instance variables, enabling a developer to easily customize the look by changing variables.

First, split the text string into several nodes (one letter per node), as shown in the following code fragment.

Source Code
def chars = for (i in [1..text.length()]) Text {
  textOrigin: TextOrigin.TOP
  content: text.substring(i - 1, i)
  strokeWidth: 2
  stroke: Color.BLUE
  fill: Color.YELLOW
  font: Font { size: 200 }
}
  

Figure 1: Creating a Text Node for Each Letter

Second, lay out all nodes using the custom layout mechanism. The following code accomplishes this task.

Source Code
onLayout: function(group) {
  if (not anim.running) {
    var width:  Number;
    var height: Number;
    for (node in group.content) {
      node.translateX = width;
      width += node.boundsInLocal.width;
      if (height < node.boundsInLocal.height) {
        height = node.boundsInLocal.height
      }
    }
    width  = 0.5 * (group.scene.width  - width);
    height = 0.5 * (group.scene.height - height);
    for (node in group.content) {
      node.translateX += width;
      node.translateY = height;
    }
  }
}
  

Figure 2: Laying Out All Nodes

Finally, the key frames are updated on the animation launch, as shown in the following code fragment.

Source Code
anim.keyFrames = for (node in chars) {
  def i1 = Interpolator.SPLINE(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble());
  def i2 = Interpolator.SPLINE(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble());

  def op1 = node.opacity;      def op2 = 0.0;
  def sx1 = node.scaleX;       def sx2 = 0.0;
  def sy1 = node.scaleY;       def sy2 = 0.0;
  def tx1 = node.translateX;   def tx2 = rnd.nextDouble() * node.scene.width;
  def ty1 = node.translateY;   def ty2 = rnd.nextDouble() * node.scene.height;
  def ro1 = node.rotate;       def ro2 = rnd.nextDouble() * 2880 - 1440;
  [
    at (3s) {
      node.opacity    => op2 tween i2;
      node.scaleX     => sx2 tween i2;
      node.scaleY     => sy2 tween i2;
      node.translateX => tx2 tween i2;
      node.translateY => ty2 tween i2;
      node.rotate     => ro2 tween i2;
      node.stroke     => node.stroke;
      node.fill       => node.fill;
    }
    at (6s) {
      node.opacity    => op1 tween i1;
      node.scaleX     => sx1 tween i1;
      node.scaleY     => sy1 tween i1;
      node.translateX => tx1 tween i1;
      node.translateY => ty1 tween i1;
      node.rotate     => ro1 tween i1;
      node.stroke     => Color.color(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble());
      node.fill       => Color.color(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble());
    }
  ]
}
  

Figure 3: Updating the Key Frames

Customizing the Code

To further customize the flying letters, change the text instance variable or other instance variables of the Text nodes. For example, you can apply a gradient fill effect, as shown in Figure 5.

Source Code
def text = "Sergey Malenkov";
def chars = for (i in [1..text.length()]) Text {
  textOrigin: TextOrigin.TOP
  content: text.substring(i - 1, i)
  stroke: Color.ORANGE
  fill: LinearGradient {
    endX: 0
    stops: [
      Stop { offset: 0   color: Color.ORANGE }
      Stop { offset: 1   color: Color.YELLOW }
    ]
  }
  font: Font { size: 50 }
}
  

Figure 4: Customizing the Text Instance Variables