Tutorial: Parallax Simplified

Tutorial: Parallax Simplified

In this week’s Tuesday Tutorial, Corona Ambassador Brent Sorrentino explores the basics of parallax scrolling and walks you through a demo project to implement a customizable touch-and-drag parallax view in your app.

We Live in a Parallax World

Parallax is a broad term with numerous definitions in its application to astronomy, photography, and optics. In a 2-dimensional scrolling simulation, which especially concerns mobile game developers, parallax is used to provide a sense of “distance” between the camera and moving objects that recede back into the simulated space. This method has been used since the 16-bit Nintendo days and it continues to be used in modern apps like “Angry Birds” and “Squids”.

Corona’s usage of display groups as specifically-ordered layers makes parallax scrolling simple to implement, at least in theory — but many developers get confused in the coding stage. This tutorial and demo project aims to address that!

1. Getting Started

The first thing we’ll do is configure our parallax “layers” as display groups. Open a blank project in Corona and copy the following lines into “main.lua”:
[gist id=”3900426″]

Notice that each parallax layer has a specified “distanceRatio” parameter — this is very important!  We’ll adjust these values to determine the scrolling speed of each layer in relation to 1:1 scrolling (1.0) of the foreground. If a specific layer should be exempted from parallax scrolling, simply leave this parameter undeclared.  For example, in the overall hierarchy of your display group setup, you’ll likely have one or two layers which do not scroll — a UI/button/widget layer for example. Only layers with the “distanceRatio” parameter will scroll within the parallax behavior!

Additional note: the “distanceRatio” parameter can be greater than 1.0 if you wish. Using a value such as 1.4 will provide a layer which moves faster than the foreground layer, for example, a layer of opaque mist which resides between the camera and the foreground.

2. Setting Scroll Limits

In almost every scenario, we’ll need to constrain the boundaries of the “world” — that is, limit how far the user can drag the world in any direction. This is accomplished with the following four variables, each of which we’ll declare as parameters of the stage for easy access later in the project.  Add the following line to your own project now:
[gist id=”3900792″]

These four parameters simply limit the world’s boundaries. xMin will typically be a negative value, defining how far to the left the world can scroll. xMax limits the movement to the right, and yMin and yMax limit upward and downward scrolling respectively.

Note that all limits are in pixels, not percentage, and they constrain the foreground 1:1 layer’s movement. Layers behind or in front will not be constrained by these limits.

3. Add Layers to a Containing Table

We’ll now add our parallax layers to a containing table named paraGroups, and that table will be set as a sub-table of the stage. Copy the following lines into your project:
[gist id=”3900896″]

In this code block, we first declare the containing table. Then we loop through all display groups, each of which is a child of the core stage. However, only the layers with a “distanceRatio” parameter are added — all other layers, such as those you wish to exempt from scrolling, will be ignored.

4. Populate the World

In the following code, we’ll set a gradient background and add some sample objects to our world. I won’t go through this line-by-line since your own implementation of a parallax world will vary — just note that we’re adding one object to each parallax layer, but you’ll want to populate your world with many objects and images.
[gist id=”3900988″]

5. Implementing Touch and Drag

A scrollable world isn’t much good if you can’t actually move it! The following function handles the screen touch “began” and “moved” phases and manipulates all groups in the paraGroups table as you drag around the screen. Please copy this function and touch listener into your project:
[gist id=”3901106″]

Again, I won’t walk through this function line-by-line, but I’ll summarize its behavior. In the began phase, we loop through the paraGroups table and set the proper “offset” X/Y values in relation to the foreground reference group. This is necessary to place all layers in a positional reference based on the position of the core 1:1 group.

The moved phase is where the real action occurs. First, we determine the xDif and yDif “delta” values in which to move the reference group, based on where the user started the touch and how far the touch has moved since the previous phase check.

Next, we check if either the resulting X or Y position will be outside the world boundaries declared in Step #2 above. If any limit has been exceeded, we pre-adjust the xDif or yDif value so that the world will never move beyond its limits. As noted in the code, you may comment out these lines if you don’t wish to (or don’t need to) impose any scrolling limits.

Finally, we loop through the paraGroups table and adjust the position of each parallax layer in relation to the distanceRatio value you specified for the group. The foreground group, with a distance ratio of 1.0, will follow/track the user’s touch exactly.  Each layer behind or in front will move at the distance ratio that you specified.  Perhaps the best part is that you can tweak and adjust your ratios in one place (where you declare the groups) and see the result reflected immediately in your app.  After all, setting the proper ratios is based more on “feel” than an exact mathematical calculation, and you’ll need to determine which ratio feels best for each layer depending on the images/object within it and the “implied distance” from the camera.

It’s that simple! We now have a flexible method to implement a scrolling touch-and-drag parallax world with no practical limitation on the number of layers. Adjusting the movement ratio of each layer is equally simple and can be set dynamically from a database or a text file if you wish.

6. Cleaning Up

Don’t forget to clean up your layer references, especially if you’re using a scene manager like Storyboard or Director. In addition to using standard cleanup practices for each respective scene manager, we’ll need to clear out the paraGroups table that we created since it contains references to your parallax display groups, all of which will likely be removed when you change scenes.  Likewise, don’t forget to remove the touch listener, unless you’re implementing this entire method at a global level and intend to use it from scene to scene.
[gist id=”3901370″]

Looking Forward!

In a future Tuesday Tutorial, we’ll build upon this method and introduce a convenient zoom feature to dynamically zoom your parallax layers in/out at the proper dimensional ratios.  Until then, please test out and tweak this demo project to your liking, and post your questions and suggestions below.

Brent Sorrentino

Brent Sorrentino is a full-time Developer Evangelist and technical writer who assists others with game development, code, and in overcoming other challenges to help them bring their app dreams to life.

  • Jordan Schuetz
    Posted at 14:40h, 16 October

    Thats a neat little sample. Thanks!

  • Xavier
    Posted at 15:33h, 16 October

    Thanks for the sample, really appreciated. Now hands on to put it at work with a new project!

  • Chris Leyton
    Posted at 02:18h, 17 October

    Just wanted to say thanks for continuing to provide tutorials on more advanced subjects – exactly what Corona needs.

  • Dave Baxter
    Posted at 02:22h, 17 October

    How come everyone uses display.contentWidth/2 instead of display.contentCenterX ?

    Also I think all examples on the blog should be explained with StoryBoard in mind, so we know where to put things.


  • Byron
    Posted at 07:07h, 17 October

    Any chance you could provide an example or source files? Love to see the tutorial in action… even a movie clip of the results we should expect.

    Great tutorial…. please keep it up!


  • Brent Sorrentino
    Posted at 09:06h, 17 October

    Hi Byron,
    I was hoping that readers would follow through the tutorial step-by-step, copying the code blocks to create the entire project. 🙂 However, in the interest of saving time, I’ve created a link to the sample project below… you’ll just need to rename it to “main.lua” and run it in the Simulator. Enjoy!


    • Byron
      Posted at 14:36h, 18 October

      Thank you!

      • kadajsha
        Posted at 23:30h, 25 January

        link is dead -_-

    • Thet Paing Soe
      Posted at 01:15h, 08 February


  • Brent Sorrentino
    Posted at 09:14h, 17 October

    Hi Dave Baxter,
    Thanks for your comments! “display.contentWidth/2” and “display.contentWidth * 0.5” are exactly the same as “display.contentCenterX”… it’s a matter of personal preference, nothing more.

    In terms of Storyboard, this sample project is “separate” from that because there are still a vast number of developers who prefer (and use) Director, or don’t necessarily use a scene manager at all. So, I hesitate to put everything into context of Storyboard, as it might be confusing to those who don’t use it. But, your point is valid and when I write future tutorials, I’ll try to design them “Storyboard friendly”… I believe this one is already.

  • Richie G
    Posted at 04:58h, 19 October

    Have CoronaLabs got any update on this bug

    14030 – Showstopper – Graphics Jitter

    Most endless runners utilise parallax scrolling, so before you delve into it, maybe check this first..

    Waiting patiently since May

  • Brent Sorrentino
    Posted at 09:19h, 19 October

    Hello Richie,

    Can you please update me on the method you’re using for this? Are you still using LevelHelper? Are you trying to move dozens of physical objects across the screen by “physical” methods (setting velocity, etc.)? Are you trying to move them by “transition” methods? Constant runtime X/Y position updating? I read through the forum thread and the bug report but it’s hard to tell what the real issue might be.

    I have a parallax 2D side-scroller under development, and there’s no stutter at all, not at 30FPS or 60FPS. The “world” consists of 100% physical objects which move across the screen, and there are dozens of them on the screen at the same time. But, these objects need to be managed (activated and deactivated) very carefully for sheer device performance issues… something which LevelHelper might not do. What I mean is, you won’t be able to have 300 physical objects moving around a simulation, some of them on-screen, many of them off-screen, and expect smooth performance on a mobile device.

    If you’re trying to move your entire parallax world via transitions, then I might suggest there are “better ways” (as in, what I’m doing in my endless runner, using physics methods). Physical objects shouldn’t be moved by transition anyway, if you need collision detection… that has been proven as unreliable within Box2D and its collision detection system.

    All of that being said, I should note to other readers that this parallax module isn’t designed for use in an “endless runner” or huge side-scrolling game. This is better suited for a game/app with “worlds” of a reasonably limited size… something like Angry Birds… where you can scroll around a level but its limits are are not “endless”. An endless runner or side-scroller like Super Mario absolutely requires a different approach!


    • Richie G
      Posted at 02:46h, 26 October

      Hi Brent

      Thanks for your response. You probably found from trawling the forums that this graphics jitter is a common problem which people have tried to get around using the different approaches you mentioned… ie. physics velocity, transitions, :translate() etc etc… .but to no avail.

      I am not using any 3rd party tools.

      Its not a frame drop issues, as my fps is pretty steady… at 30fps… and its not a sluggish scroll.. its more like vibrating display objects as they move. The example attached the bug is a very simple example and shows the symptoms clearly.

      I have had people test my app and they dont necessarily notice it… I do :/

      The response to that bug I came across suggests its an engine issue.. but it has been sitting in the “Whats Next” bug list since May.


  • Chris Leyton
    Posted at 09:50h, 19 October

    Hey Brent – perhaps that’s the next tutorial then 🙂

  • George
    Posted at 22:21h, 10 November

    How can I replace the NEW RECT for Images??

    • Brent Sorrentino
      Posted at 09:08h, 12 November

      Hi George,
      Putting together your own parallax world is basically up to you, in terms of content. You can place images (using the image APIs), text, vector shapes, etc. into the proper parallax display groups, and it should work fine. Each developer’s implementation will vary and this tutorial is primarily about laying the “groundwork” for a parallax scrolling environment.

      • Julian
        Posted at 09:21h, 13 November

        Hi Brent

        I’m also having trouble replacing the NEW RECT with images

        I changed to:
        local object00 = display.newImageRect( imgDir.. “p1_bg_4.png”, 1024, 768 );
        BG_4.x = 512; BG_4.y = 384; BG_4.alpha = 1; BG_4.oldAlpha = 1

        But its not working. What else do I have to change in your code?


  • Julian
    Posted at 09:57h, 13 November

    Hi Brent

    Thanks for the tutorial!
    I’m also having trouble with replacing the RECT with my own images. I tried the follwing:
    local object00 = display.newImageRect( imgDir.. “p1_back00.png”, 1024, 768 );
    back00.x = 1000; back00.y = 384; back00.alpha = 1; back00.oldAlpha = 1

    Do I have to change something else in your code?

    • Brent Sorrentino
      Posted at 15:45h, 13 November

      Hi Julian,
      Is it showing the image, but the image doesn’t actually scroll? In your code, I see that you’re not inserting the image into one of the parallax display groups… this is essential to the functionality of the module. You can insert images manually using “displayGroup:insert( object )”, or you can simply provide the display group as the FIRST argument in the newImageRect() API (before you specify the location and name of the image).

      Let me know if you still have issues after doing this… thanks!

  • Julian
    Posted at 01:28h, 14 November

    Hi Brent
    Thanks for the answer! I tried both ways to insert images into the parallax groups, but still its showing the images without scrolling.

    I’m using kwik2 (photoshop plugin) for my main build. Is it possible that some code from there conflicts with the parallax-module? For example the images are already in another group (menuGroup). I also deleted this group, but no scrolling either…

    heres is my project, if you want to have a look at it


    thanks a lot!

    • Brent Sorrentino
      Posted at 08:40h, 14 November

      Hi Julian,
      I checked out your file “page_1.lua” and it appears that you’re “cleaning up” the module immediately after you set it up. You do this on Line 54. The cleanup step is outlined as #6 in my tutorial, and it’s necessary to perform it, but not where you currently have it in your code. The idea is to clean up the module when you change scenes in Director or similar.

      Basically what you’re doing in your file is adding a touch sensor (listener) to the screen (stage), then a few lines later, you’re removing that same sensor. This probably explains why nothing is happening. 🙂

      • Julian
        Posted at 09:20h, 14 November

        Where exactly do I have to put in the ‘cleanup’? I tried it under Director.lua –> change scenes. But still its not working…

        • Brent Sorrentino
          Posted at 10:03h, 14 November

          Hi Julian,
          Sorry, but I can’t help specifically here. I haven’t used “core” Director in a long, long time (I use a heavily-modified and bare-bones trimmed down version of it that I created for my own projects). Thus, I don’t know how the current version handles cleanup. Your best option is to ask about this in the Director sub-forum on the Corona site, as there are still MANY users of the Director module and they should know how to help you.

        • Adrie
          Posted at 08:02h, 26 November

          Hi Jullian,

          Dis you get your Kwik2 problem solved?
          I’m struggling with the same issue.
          I’m also trying to set up a parallax page. I managed to populate Brent’s parallax simplified file with some sample images from another project. That’s working the way I want: made a simple horizontal scroll with layers scrolling in different speed.
          As I like to organize my complete project in Kwik I’m trying to get the same result with layers and groups in Kwik and add external code where needed. I managed to create the layers and by creating groups I get the same groups as in the parallax simplified but now I’m stuck because I don’t quite understand how and where to place the external code in the Director class setup in the Kwik file.

          I’m curious how you dealt with this

          Kind Regards


  • Julian
    Posted at 04:32h, 14 November

    by the way, do you think its possible to add a ‘delayed stop’ like its common with scrolling to the module or is that a totally different approach?

    • Brent Sorrentino
      Posted at 08:44h, 14 November

      I’m not sure I understand what you mean by this… would that be a sort of “flick the screen” and the motion goes in the direction of the flick, then gradually slows to a stop? It’s not implemented in there now, but that’s a good idea for a future version! I eventually want to add pinch-zoom and 2-finger-rotate to this, but I’m not sure when I’ll get around to it.

  • Julian
    Posted at 08:51h, 14 November

    thanks Brent!

    ” “flick the screen” and the motion goes in the direction of the flick, then gradually slows to a stop?”
    thats exactly it. I was just wondering if its possible to add it later with your module or if a totally different aproach would be necessary.

    • Brent Sorrentino
      Posted at 09:23h, 14 November

      Hi Julian… short answer is “yes”, it would be possible with my module. I’d just need to add quite a bit more functionality like sensing if the motion is a “flick” or a “slow drag”… I already have some of that capability built into my “scrolling slide view” module in the Code Exchange, but I’d have to extract and improve upon it for this parallax thing. It’s an interesting concept but I’m not sure how much “demand” there is for it, and thus, I might not get around to it for awhile… is this something you really need for your app, or are you just curious if it’s possible?

      • Julian
        Posted at 09:32h, 14 November

        Its something I’d like to add to my app, but its not crucial.

      • Claude
        Posted at 08:22h, 15 September

        Hello Brent!

        Like Julian and Olivier I would really like if you can help us or show us how to modify your module to add the Flick and Slow drag effect to it!

        I’ve been struggling to get it working without success!

        That would be really appreciated!


  • Olivier
    Posted at 06:10h, 11 January

    Hi Brent
    Thank you for this great tutorial 🙂
    I tried without success to add to your example the smooth effect which exist in scrollView widget :
    when you stop drag, the content slows down instead of stopping immediately. (the velocity decrease progressively)
    Could you explain us how to do this in a parallax world like your example?
    Thank you so much

  • Olivier
    Posted at 07:19h, 11 January

    Ooops sorry,
    It seems that Julian already asked the same thing…
    So It’s something I’d like to add to my app too…
    Thanks again

  • Olivier
    Posted at 01:24h, 16 January

    In fact, it can be seen as a detail but it’s such detail which makes your app really professional…
    Thanks again

  • Julian
    Posted at 02:01h, 04 February

    I’ve implemented a ‘magnetic drop area’, lets say “D”, to ‘back02’. Now I have a dragable object “A” which is not part of any parallax group. Is there a way to insert object “A” to group ‘back02’ after it was ‘dropped’ to “D” so thats its moving with the parallax background?

    thanks for any help!

  • Julian
    Posted at 09:57h, 06 February

    How can I calculate the relative movement of each group from before and after one scrolling event?

    The only thing I get is the absolut position of each layer, but I couldn’t figure out how to calculate the relative movement.

    Any ideas?

    • Brent Sorrentino
      Posted at 08:55h, 08 February

      Hi Julian,
      I’ll try to answer both of your questions here:
      For a “magnetic drop” area, yes, you should be able to insert and remove objects from any parallax group, into or out of a group which isn’t part of the parallax scrolling.

      Not sure what you mean by “relative movement”. Are you trying to build a kind of scenario where the user “swipes” the screen (instead of just touch-dragging) and the world has some kind of residual movement, i.e. they can swipe around the world and it slows down gradually after they’ve lifted their finger off? Please explain the usage you’re trying to implement, and hopefully I can help you.

  • Julian
    Posted at 12:26h, 03 March

    Hi Brent,

    thanks for your answer! unfortunately I just saw it yesterday…

    Theres a problem with the ‘magnetic drop’. Instead of removing an object from a group and insert it to another, I tried to change the distanceRatio value from a group.

    its working if I switch from a background with a distanceRatio bigger than ‘0’ to another bigger than ‘0’. But theres a strange behaviour if I drop an object back to a distanceRatio with the value “0”.

    The object is moving to the right position but after starting scrolling the parallax-background the object jumpes quickly to another position.

    Theres another thing: When I test the parallax-module on a device it behaves strangely when touching the screen with two fingers (jumps forth and back). Is it possible to transform this multitouch event to a ‘single-touch’ event?

    I hope its clear enough…

  • Claude Turcot
    Posted at 06:32h, 16 October

    Brent or anybody, did somebody found a way to make a “flick”, “bounce at the end”, “slow scroll” effects when scrolling from left to right to be added in the touchscreen function?

    I’ve been using this function for my game, It’s working really good but I’m missing that iOS effects when I scroll and since I’m almost ready to publish my game I really need some help trying a way to enhance this function to achieve that!

    Can somebody help?



    • Brent Sorrentino
      Posted at 15:30h, 22 October

      Hi Claude,
      I haven’t yet implemented this, but the process should (in theory) not be too difficult… at least the “slow scroll” functionality. I would recommend that you simply check if the “world” is beyond its limits, then as the user continues to scroll, a fractional percentage of movement is applied. Then, when the user releases (lifts off touch), run a function that transitions the scene back to its limit value, with an easing-out transition so it looks more natural.

      Hope this helps somewhat. I may be able to revisit this module eventually and add more functionality, but I don’t have an ETA on that.


  • Radim
    Posted at 11:35h, 12 May


    I’m using composer and I’m calling overlay scene which is “game options” scene. However this overlay scene shows bellow parallax layers and I need it above all layers.

    part of my code:

    stage:insert(composer.stage) — current stage – static background
    stage:insert(back) — parallax background
    stage:insert(foreground) — parallax foreground
    stage:insert(scoresView) — score board above all layers

    now I need to insert composer overlay scene above all scene’s layers. How to do that?

    • Brent
      Posted at 13:31h, 12 May

      Hi Radim,
      Since you’re inserting the composer.stage first, it will reside behind the others. You should declare that at the end of your series of :insert(s), so it remains at the front.

  • Nigel
    Posted at 22:40h, 21 June

    Hi, Could you please modify this for pinch zooming ?,
    Ive tried combining this coronalabs.com/blog/2013/01/22/implementing-pinch-zoom-rotate/
    but the layers dont seem to behave correctly, Be great for any info.


  • Parallax-scrolling effect … in ANSI | Break Into Chat
    Posted at 20:10h, 14 January

    […] also plan to play around with using a parallax ratio, similar to what I saw in this tutorial, instead of a hard number. I’m thinking I could then compare the ratio to the step interval […]