transition2: A customizable extension to the transition library

UPDATE:  transition2 is now a full standalone rewrite of the transition library instead of just an extension.


The last couple of days I’ve been working on  transition2 : A customizable extension for the existing transition library (with an imaginative name  :)).

To start with, transition2 introduces the transition functions color(), glow(), bounce() and moveSine(), but it can also easily be extended further with custom transition functions. Here’s an example of bounce() and glow() combined with a regular for scaling and rotation:


I’ve written quite a long blog post about it where you can read more about why and how I did this:

If you’re interested in trying it out you can find the full source code here:

I would love to get some feedback from you! If transition2 turns out to be useful to other Corona devs I’ll definitely consider extending it with more transition functions and turn it into a Corona plugin.

Thanks for reading!


Ooooh.  I can see I’m gonna be having some fun playing with that :slight_smile:

Good work!

Thanks!  :slight_smile: Let me know if you have any questions or if something doesn’t work as expected.

That’s awesome!!! 


Thanks Rob! I hope people will find it at least somewhat awesome efter playing around with it for a while.  :lol: I will continue working on it from time to time, so I’ll post here as soon as there are any updates available.

I just added another transition function called zRotate(). You guessed it, it rotates a display object in the z dimension around either the x or y axis. Requires the display object to have a path with four nodes (x1, y1) to (x4, y4), like images and rects do.

A simple vertical rotation:

transition.zRotate(coronaLogo, { degrees = 360, time = 3000, iterations = 0, transition = easing.inOutSine, reverse = true, })


A little more complex example of a horizontal rotation with a color effect at the end of each iteration. Note that this example utilizes two new params: iterationDelay and onIterationComplete  (which is not the same as onRepeat).

transition.zRotate(coronaLogo, { degrees = -1440, time = 4000, iterations = 0, transition = easing.inOutQuad, perspective = 0.75, horizontal = true, iterationDelay = 1000, onIterationComplete = function() transition.color(coronaLogo, { startColor = orange, endColor = white, time = 500, transition = easing.inOutSine, reverse = true, }) end, })


Finally, you can do pretty cool stuff like this bounce/glow/zRotate/to combo:  :slight_smile:

transition.zRotate(coronaLogo, { degrees = 360, time = 4000, iterations = 0, transition = easing.inOutSine, reverse = true, horizontal = true, }) transition.glow(coronaLogo, { startColor = white, endColor = orange, time = 2000, }) transition.bounce(coronaLogo, { height = display.contentHeight - 400, time = 2000, iterations = 0, }), { rotation = 360, time = 2000, iterations = 0, }), { xScale = 2.5, yScale = 2.5, transition = easing.continuousLoop, time = 2000, iterations = 0, })


You can find the link to the source code in the first post of this topic. Enjoy!

UPDATE Jul 06, 2017:  zRotate() can now also be applied statically by setting param static=true. This will immediately rotate the display object to its final rotation without actually starting a transition. Using static=true makes params like time, iterations, transition etcetera useless. Example:

transition.zRotate(coronaLogo, { degrees = 45, horizontal = true, static = true })

wish all this was part of the original transition library


@Markus, are you checking if the object is still valid in your library extension?  I mean simple check in enterframe?

Yes, I try to do that, but since the general algorithm of transition2 is agnostic to the target object type this check must be done in the implementation of each transition2 extension function. What I mean is that transition2 is not limited to just display objects, although that will be the most common use case.

That’s where the **cancelWhen() **function comes in handy. For example, in transition2-color.lua I’ve implemented it like this:

cancelWhen = function(displayObject, params) -- If the setFillColor no longer exists, the display object has been removed and there is no need for the transition to go on return not displayObject.setFillColor end

What will happen is that cancelWhen will be called on each frame, and whenever it returns true the transition will be auto-cancelled. I wish there was some specific property on display objects that could be checked to see if an object is still valid, but I don’t think there is so you have to just check some property or function that you know should be set.

Note that since cancelWhen is implemented on the transition extension itself like this, you won’t have to do it yourself every time you use the transition2.color() function.

However, you can implement cancelWhen on each individual transition too if you want other parts of your context to affect when a transition should be cancelled. In this case, cancelWhen accepts no parameters because you’ll already have access to all the context you need.


local weAreDone = false transition.color(obj, { startColor = {1, 0, 0, 1}, endColor = {0, 0, 1, 1}, time = 1000, iterations = 0, cancelWhen = function() return weAreDone end }) timer.performWithDelay(10000, function() weAreDone = true end

Hope that made sense.  :slight_smile:

Some more modifications:

**onRepeat(target, params) - **Now accepts params as second param, to allow you to change params between iterations. Note that only transition specific params can (or should) be changed this way, like endColor of color() or degrees of zRotate(). For example, changing params.time will have no effect.

onIterationComplete(target, params) - Also accepting params as second param.

onIterationStart(target, params) - New function that will be called before the start of every iteration, including the first one. Is really only a convenience function when you want to do the same thing in onRepeat() and onStart().

Here’s an example of what you can do after these modifications where I change the rotation direction and endColor for every iteration.

UPDATE Jul 07, 2017 : Added recaculcateOnIteration=true to example code. Without it, the modification to params.endColor in onIterationStart won’t be applied as expected.

transition.zRotate(coronaLogo, { degrees = 360, iterations = 0, time = 1000, reverse = true, horizontal = false, transitionReverse = easing.inOutQuad, onRepeat = function(target, params) params.horizontal = not params.horizontal end }) transition.glow(coronaLogo, { startColor = orange, time = 2000, onIterationStart = function(target, params) params.endColor = {math.random(), math.random(), math.random(), 1} end, recalculateOnIteration = true })


local isDisplayObject =  ( "table" == type( targetObject.\_class ) )

Is a real fast “is display object” check you could use in enterframe()

You could perhaps check on start and if object being transitioned was display object then cancel transition if that property went false.  If not display object then you would check for nil and cancel if variable went out of scope.

Simple checks to harden code.

Thanks, but I don’t see how that could possibly work. The display object will still be a table even after it’s been removed, just that it won’t have all the normal properties and functions of a display object. Example:

print("table" == type(targetObject.\_class )) -- "true" targetObject:removeSelf() print("table" == type(targetObject.\_class )) -- Still "true"...

moveBungy(), another little transition function that moves a display object by streching and compressing it. Not sure about the usefulness of this one, but it was fun playing around with it… :) I’m still trying to figure out different ways of using the main transition2 algorithm and finding flaws in it, and the best way to do that is to just implement new transition functions!

UPDATE Jul 07, 2017 : Added recaculcateOnIteration=true to example code. Without it, the param modifications in onIterationStart won’t be applied as expected.

local iteration = 0 local offsets = { y = {-200, 0, -200, 0}, x = {0, -200, 0, 200}, } transition.moveBungy(coronaLogo, { time = 500, iterations = 0, iterationDelay = 100, delay = 500, onIterationStart = function(target, params) params.offsetX = offsets.x[iteration % 4 + 1] params.offsetY = offsets.y[iteration % 4 + 1] iteration = iteration + 1 end, recalculateOnIteration = true })


After giving it some thought I decided to override some of the transition functions from the transition library, most importantly The reason behind this is that I want to be able to use transition2 specific params like reverse, transitionReverse, iterationDelay and cancelWhen on transitions created with as well. It also makes to() support auto-cancelling of transitions when display objects are removed, which is quite nice.

If you try out my overriding to() implementation, please report bugs and/or behavior that differs from the original function. I’ve tried to replicate the behavior, but there are many cases to test so I might very well have missed something.  :stuck_out_tongue:

Here’s an example of a blur/rotate/scale transition with to() that makes use of the new transition2 param ‘reverse’:, { rotation = 360, time = 1500, iterations = 0, reverse = true, transition = easing.inOutSine, }), { xScale = 2, yScale = 2, transition = easing.continuousLoop, time = 3000, iterations = 0, }) coronaLogo.fill.effect = "filter.blurGaussian" coronaLogo.fill.effect.horizontal.blurSize = 15 coronaLogo.fill.effect.vertical.blurSize = 15, { time = 1500, blurSize = 0, reverse = true, iterations = 0, transition = easing.inSine }), { time = 1500, blurSize = 0, reverse = true, iterations = 0, transition = easing.inSine })


This shows the path of an image being transitioned:, { time = 1000, x1 = 100, x2 = -100, x3 = 100, x4 = -100, y1 = -50, y2 = 50, y3 = 50, y4 = -50, transition = easing.inSine, reverse = true, transitionReverse = easing.outSine, iterations = 0, })


transition2 just turned into a full standalone rewrite of the transition library. I was never my original intention, but now it has happened anyway…  :slight_smile:

Implementing my own function made me realize that I could also implement the remaining functions pretty quickly and then just cut the dependency to the transition library completely.

By doing this, the transition2 library becomes more focused on its own functionality, rather than also have to wrap and support the functionality of the transition library. Now, when a bug or problem is found when using transition2 we can also be 100% certain that the problem belongs to transition2 which makes it easier to hunt down and fix.

Note that you can still run transition2 in parallel with transition, just require it where you want to use it.

Full source code with quite decent documentation:

Hi Markus,

I’ve decided to drop the latest version of transition2 into my current project but have run into a problem.

I copied the transition2lib folder and the transition2.lua file into the root of my project, and required it using

transition = require ("transition2")

But when I run it I get the following error…

ERROR: Runtime error

                    module ‘transition2-main’ not found:

                    no field package.preload[‘transition2-main’]

                    no file ‘/Users/home/Library/Application Support/Corona/Simulator/Plugins/transition2-main.lua’

                    no file ‘transition2-main.lua’

                    no file ‘/Applications/CoronaSDK-3085/Corona’

                    no file ‘./transition2-main.lua’

                    no file ‘./transition2lib/transition2-main.lua’

                    no file ‘/Users/home/Library/Application Support/Corona/Simulator/Plugins/transition2-main.dylib’

                    no file ‘./transition2-main.dylib’

                    no file ‘/Applications/CoronaSDK-3085/Corona’

                    stack traceback:

                    [C]: in function ‘require’

                    ?: in function ‘require’

                    transition2.lua:16: in main chunk

                    [C]: in function ‘require’

                    ?: in function ‘require’

                    main.lua:14: in main chunk

The previous version of transition2 works fine.

I’ve created a completely new project with the following in the main.lua

transition = require("transition2") local shouldCancel = false local bob = display.newCircle(display.contentWidth+30, display.contentHeight/2, 30) transition.moveSine(bob, { radiusY = 50, time = 2500, startDegreesX = 180, startDegreesY = 90, iterations = 6, }),{time=6000, x=-30})

With the older version of transition2 it works, but the new version is giving the same error as above.

Thanks for reporting. I will have a look at it as soon as I can. My guess is that it’s the package.path in transition2.lua that doesn’t work for some reason. You can try to change the requires in transition lua to explicitly point to the transition2lib folder and see if it helps.

For example, change require(“transition2-main”) to require(“transition2lib.transition2-main”).

Let me know how it turns out. Thanks!

Hi Markus,

Thanks for the help.  Sorry for the delay in getting back to you.

It’s now working, but I ended up having to go through every single transition2 file, searching for any “require” statements and manually adding the path to them.  So yeah, it definitely looks like an issue with the package.path functionality.

A quick question.  Is it possible to use onIterationComplete with the new

For example, using to move an object from one side of the screen to the other but after each iteration to change the y pos of the object to a random value.   Can’t seem to get it to work.

I’ve changed the code back to use the transition2lib prefix for all require statements. I have no idea why the package.path worked for me but not for you. If anybody has a good solution for using relative paths in requires, I’m all ears.  :slight_smile:

Good find! When I tried it out myself I realized that modifying the target object between iterations didn’t work for any of the transition functions… The problem is that the start and end values for each transition are only calculated once on transition start. So for example, if you try to set the displayObject.y value in the onIterationComplete() function, it will be overridden by the precalculated start y value at the beginning of the next iteration.

UPDATE Jul 04, 2017

A fix for this has been implemented. In your case, I understand that you had a to() transition for the x value and you simply wanted to change the y value between each iteration. That was a bug preventing it from working, and code like the following should now work:, { x = display.contentWidth, onRepeat = function(displayObject, params) displayObject.y = math.random(0, display.contentHeight) end, time = 1000, iterations = 0, })

However, let’s assume that you also want apply a transition to the y value, while still randomizing it in the onRepeat function. You would probably write your code similar to this:, { x = display.contentWidth, y = coronaLogo.y + 100, onRepeat = function(displayObject, params) displayObject.y = math.random(0, display.contentHeight) params.y = displayObject.y + 100 end, time = 1000, iterations = 0, })

That won’t work. Because the start/end transition values for y have already been calculated when the transition started, so changing displayObject.y or params.y in onRepeat will have no effect more than just temporarily moving the object to the new y position. As soon as the next iteration starts, the y value you just set will be overridden by the precalculated one.

The solution to this is a new control param called recalculateOnIteration., { x = display.contentWidth, y = coronaLogo.y + 100, onRepeat = function(displayObject, params) displayObject.y = math.random(0, display.contentHeight) params.y = displayObject.y + 100 -- Note that we must also reset the x value here, -- since the start x value for the next iteration -- will be calculated from the current position of the display object. displayObject.x = 0 end, time = 1000, iterations = 0, recalculateOnIteration = true })

The default value of recalculateOnIteration is false since that is how the functions of the original transition library work, and the goal of transition2 is to behave the same in default mode.