Introduction

This tutorial will guide you through the steps to set up a simple animation and teach you the basics of timing, positioning, and \LaTeX. It is assumed that you are familiar with Haskell but you don't have to know anything about reanimate or animation in general.

All of the code snippets are interactive and require JavaScript.

Tutorial Goal

By the end of this tutorial, we will have a short animation that shows an equation and then draws an explanation of each term:


Step 1: Blank canvas

To begin with, let's draw a light-blue background and make it easier to see our animation canvas. We can do this in the imperative scene context by creating a sprite containing the background color. All SVG color names are supported so give mistyrose, aquamarine or burlywood a try. Don't try hex codes, though, as they are beyond the scope of this tutorial.

animation :: Animation
animation = scene $ do
  newSpriteSVG_ $
    mkBackground "lightblue"

API references: scene, newSpriteSVG_, mkBackground, Animation.

Step 2: Typesetting an equation

Let's start by refactoring the previous animation and create a separate function that defines our environment. This will make it easier to modify the environment in the future:

env :: Animation -> Animation
env = addStatic bg

bg :: SVG
bg = mkBackground "lightblue"

SVG has no support for typesetting equations but reanimate offers a convenient interface to LaTeX via latexAlign :: Text -> SVG. To place the equation at the center of the screen with the right size, we'll use scale :: Double -> SVG -> SVG and center :: SVG -> SVG. Try using centerX :: SVG -> SVG, changing the scaling factor, or using a different equation like \\frac{\\infty}{\\pi}.

animation :: Animation
animation = env $ scene $ do
  newSpriteSVG_ equation
  wait 1

equation :: SVG
equation = scale 3 $ center $
  latexAlign "E = mc^2"

env :: Animation -> Animation
env = addStatic bg

bg :: SVG
bg = mkBackground "lightblue"

API references: addStatic, center, scale, latexAlign.

Step 3: Accessing glyphs

So far, so good. We have an environment with a blue background color and we have an equation in SVG format. It's almost time to start animating but first we need to split the equation SVG into smaller pieces. We can use the splitGlyphs :: [Int] -> SVG -> (SVG, SVG) to access a set of child nodes of an SVG by index.

animation :: Animation
animation = env $ scene $ do
  play $ staticFrame 1 equation
  play $ staticFrame 1 symb_e
  play $ staticFrame 1 symb_eq
  play $ staticFrame 1 symb_m
  play $ staticFrame 1 symb_c2

symb_e :: SVG
symb_e = snd $
  splitGlyphs [0] equation

symb_eq :: SVG
symb_eq = snd $
  splitGlyphs [1] equation

symb_m :: SVG
symb_m = snd $
  splitGlyphs [2] equation

symb_c2 :: SVG
symb_c2 = snd $
  splitGlyphs [3,4] equation

equation :: SVG
equation = scale 3 $ center $
  latexAlign "E = mc^2"

env :: Animation -> Animation
env = addStatic bg

bg :: SVG
bg = mkBackground "lightblue"

API references: splitGlyphs, staticFrame, play.

Step 4: Moving objects

Objects in reanimate encapsulate SVG nodes and makes it easier to animate properties such as position and scale. In the example below, we create an object from the E SVG node and 'tween' position properties to make it move around the canvas. Tweening (or inbetweening) is an animation concept that simply means moving smoothly from one value to another.

Reanimate exports many properties such as oLeftX, oTopY and oCenterXY. These properties manipulate the object's position with respect to its bounding box and margins. The position of an object is ultimately captured in the oTranslate property and resetting this property will undo any movements.

animation :: Animation
animation = env $ scene $ do
  newSpriteSVG_ equation
  obj <- oNew symb_e
  oShow obj

  oTweenS obj 1 moveTopLeft
  oTweenS obj 1 moveTopRight
  oTweenS obj 1 moveBotRight
  oTweenS obj 1 moveBotLeft
  oTweenS obj 1 moveToOrigin

moveTopLeft t = do
  oLeftX %= \origin ->
    fromToS origin screenLeft t
  oTopY %= \origin ->
    fromToS origin screenTop t

moveTopRight t = do
  oRightX %= \origin ->
    fromToS origin screenRight t
  oTopY %= \origin ->
    fromToS origin screenTop t

moveBotRight t = do
  oRightX %= \origin ->
    fromToS origin screenRight t
  oBottomY %= \origin ->
    fromToS origin screenBottom t

moveBotLeft t = do
  oLeftX %= \origin ->
    fromToS origin screenLeft t
  oBottomY %= \origin ->
    fromToS origin screenBottom t

moveToOrigin t =
  oTranslate %= lerp t (V2 0 0)

symb_e :: SVG
symb_e = snd $
  splitGlyphs [0] equation

equation :: SVG
equation = scale 3 $ center $
  latexAlign "E = mc^2"

env :: Animation -> Animation
env = addStatic bg

bg :: SVG
bg = mkBackground "lightblue"

API references: oTweenS, oNew, oShow, oLeftX, oTopY, oRightX, oBottomY, oTranslate, screenBottom, screenLeft, screenRight, screenTop.

Now that we know how to move around an object, let's move each symbol in the equation to the left of the screen and scale them down slightly to make it a better fit. The animation looks a lot better if moves overlap slightly and this can be achieved by running them in parallel with fork and inserting a wait 0.3 in between them.

animation :: Animation
animation = env $ scene $ do
  symbols <- mapM oNew
    [symb_e, symb_eq, symb_m, symb_c2]
  mapM_ oShow symbols

  forM_ (zip symbols yPositions) $
    \(obj, yPos) -> do
    fork $ oTweenS obj 1 $ \t -> do
      oScale %= \origin ->
        fromToS origin scaleFactor t
      oLeftX %= \origin ->
        fromToS origin screenLeft t
      oCenterY %= \origin ->
        fromToS origin yPos t
    wait 0.3

symb_e :: SVG
symb_e = snd $
  splitGlyphs [0] equation

symb_eq :: SVG
symb_eq = snd $
  splitGlyphs [1] equation

symb_m :: SVG
symb_m = snd $
  splitGlyphs [2] equation

symb_c2 :: SVG
symb_c2 = snd $
  splitGlyphs [3,4] equation

equation :: SVG
equation = scale 3 $ center $
  latexAlign "E = mc^2"

yPositions = [3,1,-1,-3]
scaleFactor = 0.7

env :: Animation -> Animation
env = addStatic bg

bg :: SVG
bg = mkBackground "lightblue"

API references: fork, wait.

Step 5: Drawing text

Objects are invisible by default and there are many built-in ways of showing them. Let's have a look at oFadeIn, oDraw, oScaleIn, and oGrow:

animation :: Animation
animation = env $ scene $ do
  fadeIn <- newText "oFadeIn"
  oModify fadeIn $ oCenterY .~ 3
  oShowWith fadeIn oFadeIn

  draw <- newText "oDraw"
  oModify draw $ oCenterY .~ 1
  oShowWith draw oDraw

  scaleIn <- newText "oScaleIn"
  oModify scaleIn $ oCenterY .~ -1
  oShowWith scaleIn $
    setDuration 1 . oScaleIn

  grow <- newText "oGrow"
  oModify grow $ oCenterY .~ -3
  oShowWith grow oGrow

  forM_ [fadeIn, draw, scaleIn, grow]
    $ \obj -> fork $
      oHideWith obj oFadeOut

newText =
  oNew . scale 1.5 . center . latex

env :: Animation -> Animation
env = addStatic bg .
  mapA (withStrokeWidth defaultStrokeWidth) .
  mapA (withStrokeColor "black")

bg :: SVG
bg = mkBackground "lightblue"

API references: oFadeIn, oDraw, oScaleIn, oGrow.

Let's use the oDraw function to draw text on the screen and let's make sure the text is left-aligned:

animation :: Animation
animation = env $ scene $ do
  ls <- mapM oNew [energy, equals, mass, speedOfLight]

  forM_ (zip ls yPositions) $
    \(obj, nth) -> do
    oModifyS obj $ do
      oLeftX .= -4
      oCenterY .= nth
    oShowWith obj oDraw

  forM_ ls $ \obj ->
    fork $ oHideWith obj oFadeOut

energy = scale 1.5 $ centerX $
  latex "Energy"

equals = scale 1.5 $ centerX $
  latex "equals"

mass = scale 1.5 $ centerX $
  latex "mass times"

speedOfLight = scale 1.5 $ centerX $
  latex "speed of light$^2$"

yPositions = [3,1,-1,-3]

env :: Animation -> Animation
env = addStatic (mkBackground "lightblue") .
  mapA (withStrokeWidth defaultStrokeWidth) .
  mapA (withStrokeColor "black")

 

Step 6: Putting it all together

We've reached the end and have all the parts needed to re-create the GIF shown in the beginning. To recap:

  • The Scene monad is useful for animating a sequence of events.
  • We can typeset equations with latex :: Text -> SVG.
  • SVG nodes are regular Haskell data and we can extract child nodes with splitGlyphs.
  • Objects wrap SVG nodes and make it easy to set positioning.
  • Objects have many built-in ways of being shown on canvas.

Putting everything together gives us this short animation:

animation :: Animation
animation = env $ scene $ do
  symbols <- mapM oNew
    [symb_e, symb_eq, symb_m, symb_c2]
  mapM_ oShow symbols
  wait 1

  forM_ (zip symbols yPositions) $
    \(obj, yPos) -> do
    fork $ oTweenS obj 1 $ \t -> do
      oScale %= \origin -> fromToS origin scaleFactor t
      oLeftX %= \origin -> fromToS origin screenLeft t
      oCenterY %= \origin -> fromToS origin yPos t
    wait 0.3

  wait 1

  ls <- mapM oNew [energy, equals, mass, speedOfLight]

  forM_ (zip ls yPositions) $
    \(obj, nth) -> do
    oModifyS obj $ do
      oLeftX .= -4
      oCenterY .= nth
    oShowWith obj oDraw

  wait 2

  forM_ ls $ \obj ->
    fork $ oHideWith obj oFadeOut

  forM_ (reverse symbols) $ \obj -> do
    fork $ oTweenS obj 1 $ \t -> do
      oScale %= \origin -> fromToS origin 1 t
      oTranslate %= lerp t (V2 0 0)
    wait 0.3
  wait 2

scaleFactor = 0.7

symb_e :: SVG
symb_e = snd $ splitGlyphs [0] svg

symb_eq :: SVG
symb_eq = snd $ splitGlyphs [1] svg

symb_m :: SVG
symb_m = snd $ splitGlyphs [2] svg

symb_c2 :: SVG
symb_c2 = snd $ splitGlyphs [3,4] svg

svg = scale 3 $ center $
  latexAlign "E = mc^2"

energy = scale 1.5 $ centerX $
  latex "Energy"

equals = scale 1.5 $ centerX $
  latex "equals"

mass = scale 1.5 $ centerX $
  latex "mass times"

speedOfLight = scale 1.5 $ centerX $
  latex "speed of light$^2$"

yPositions = [3,1,-1,-3]

env :: Animation -> Animation
env = addStatic (mkBackground "lightblue") .
  mapA (withStrokeWidth defaultStrokeWidth) .
  mapA (withStrokeColor "black")