PageFlip + Tweener AS3 Part 1

Update: I had a bit of an oversight in the code. This should be using the setChildIndex method instead of swap depths. I’ve added more pages, updated the code, and thanks to Harry Burt for catching that. Also thanks to him for the addition of the page turning back code. There still is an issue with depth levels if the user clicks on the pages too fast. Still haven’t had time to address that, but I think by adding a dispatcher at the midway point, the depth can be set then.

I’m sure most people have had the urge/need to do some sort of interface using that slick page flipping code that was floating around in the AS1 and AS2 heyday. They might be slightly dated, but I still like them in the right context. Obviously you can find the AS2 version out there. As for AS3, if you’re lazy efficient like me, you did a search to see what was out there. First I came across Didier Brun’s class PageFlip which is located Here. He gives a great example but you are only left with the usage code of this:

var render:Shape=new Shape();
var page0:BitmapData=new page0().bitmapData;
var page1:BitmapData=new page1().bitmapData;

render.x=50;
render.y=50;
addChild(render);

var o:Object=PageFlip.computeFlip(      new Point(60,190),    // flipped point
                                                new Point(1,1),  // of bottom-right corner
                                                page0.width,        // size of the sheet
                                                page1.height,
                                                true,            // in horizontal mode
                                                1);                         // sensibility to one

PageFlip.drawBitmapSheet(o,                         // computeflip returned object
                                                render,     // target
                                                page0,    // bitmap page 0
                                                page1);  // bitmap page 1
 

That’s great, but more then likely it left you wondering what came next. Didier’s excellent class is the foundation for all that is page flipping in AS3 (unless of course you want to reinvent the wheel). That being said, the next step was seeing hoe to put the core code into action. My next search took me to Ruben Swieringa’s site. He has an amazing and fully functioning flex application using Didier’s PageFlip class. Check it out Here. Pretty solid and feature rich, and it’s available for use. Kudos to him. However, if you are like me and you don’t work with flex, Ruben’s fine work simply serves as an example of the complexity of making the book fully interactive.

So I decided to embark on my own simpler, AS3 only version. I doubt it will be nearly as feature rich as Ruben’s flex version, but at the very least I can post a moving foundation for people to see and build upon.

I think the most exciting thing for me was how easy it was to use Tweener to flip a page using its bezier property along with it’s onUpdate callback.

[SWF]http://blog.hydrotik.com/wp-content/pageflip.swf, 500, 300[/SWF]

This is great for a flipPage() method, but obviously we’ll want to turn the page with mouse interaction as well. More of that to come later. But for the time being here’s the Page class with a simple turning animation and some events:

package com.hydrotik.book{
        import flash.display.Sprite;
        import flash.display.Shape;
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.events.MouseEvent;
        import flash.events.EventDispatcher;
        import flash.geom.Point;

        import com.foxaweb.pageflip.PageFlip;
       
        import caurina.transitions.Tweener;
       
        public class Page extends EventDispatcher {
               
                private var _scope:*;
               
                private var flip:Point;
               
                private var front:BitmapData;
               
                private var back:BitmapData;
               
                private var render:Shape;
               
                private var _width:Number;
               
                private var _height:Number;
               
                private var _page:Sprite;
               
                private var _oPage:Page;
               
                private var _name:String;
               
               
                public function getName():String {
                        return _name;
                }
               
               
                public function Page(scope:*, w:Number, h:Number, x:Number, y:Number, m1:*, m2:*, name:String) {
                        _scope = scope;
                        _width = w;
                        _height = h;
                        _oPage = this;
                       
                        trace(_oPage);
                        _name = name;
                        _page = new Sprite()
                        _page.x = x;
                        _page.y = y;
                        _scope.addChild(_page);
                       
                        render = new Shape();
                        _page.addChild(render);
                       
                        front = new BitmapData(_width, _height, true, 0);
                        front.draw(m1);
                        back = new BitmapData(_width, _height, true, 0);
                        back.draw(m2);

                        flip = new Point(_width, _height);
                        drawPage();
                        _page.addEventListener(MouseEvent.CLICK, turnPageForward);
                }
               
                private function turnPageForward(event:MouseEvent = null):void{
                        dispatchEvent(new PageEvent(PageEvent.FLIP_START, _page, _oPage, true));
                        _page.removeEventListener(MouseEvent.CLICK, turnPageForward);
                        _page.addEventListener(MouseEvent.CLICK, turnPageBack);
                       
                       
                        Tweener.addTween(flip, {
                                x:-_width,
                                y:_height,
                                _bezier:{x:0, y:0},
                                time:1.5,
                                transition:“easeinoutexpo”,
                                onUpdate:drawPage,
                                onComplete:onPageDone
                        });
                }
               
                private function turnPageBack(event:MouseEvent = null):void{
                        dispatchEvent(new PageEvent(PageEvent.FLIP_START, _page, _oPage, true));
            _page.addEventListener(MouseEvent.CLICK, turnPageForward);
            _page.removeEventListener(MouseEvent.CLICK, turnPageBack);
           
           
            Tweener.addTween(flip, {
                x:_width,
                y:_height,
                _bezier:{x:0, y:0},
                time:1.5,
                transition:“easeinoutexpo”,
                onUpdate:drawPage,
                onComplete:onPageDone
            });
        }

                private function drawPage():void{
                        render.graphics.clear()
                        var o:Object = PageFlip.computeFlip(flip,// flipped point
                                        new Point(1,1),// of bottom-right corner
                                        front.width,// size of the sheet
                                        back.height,
                                        true,// in horizontal mode
                                        1);// sensibility to one                            // sensibility to one
                        PageFlip.drawBitmapSheet(o,// computeflip returned object
                                        render,// target
                                        front,// bitmap page 0
                                        back);// bitmap page 1
                }

               
                private function onPageDone():void{
                        dispatchEvent(new PageEvent(PageEvent.FLIP_COMPLETE, _page, _oPage, true));
                }
                       
        }

}

Here’s the custom PageEvent class:

package com.hydrotik.book {
       
        import flash.events.Event;
       
       
    public class PageEvent extends Event {
               
                public static const FLIP_START:String = “flipStart”;
               
                public static const FLIP_COMPLETE:String = “flipComplete”;
               
                public var targ:*;
               
                public var page:Page;
               
                public function PageEvent(type:String, t:*, p:Page, bubbles:Boolean = false, cancelable:Boolean = false){
                        super(type, bubbles, cancelable);
                        targ = t;
                        page = p;
                }
        }
}

And here is the usage:

import com.hydrotik.book.Page;
import com.hydrotik.book.PageEvent;

var page5:Page = new Page(this, 200, 200, 250, 50, new Page5(), new Page6(), “Pages 5 and 6″);
page5.addEventListener(PageEvent.FLIP_START, onFlipStartHandler);
page5.addEventListener(PageEvent.FLIP_COMPLETE, onFlipCompleteHandler);

var page3:Page = new Page(this, 200, 200, 250, 50, new Page3(), new Page4(), “Pages 3 and 4″);
page3.addEventListener(PageEvent.FLIP_START, onFlipStartHandler);
page3.addEventListener(PageEvent.FLIP_COMPLETE, onFlipCompleteHandler);

var page1:Page = new Page(this, 200, 200, 250, 50, new Page1(), new Page2(), “Pages 1 and 2″);
page1.addEventListener(PageEvent.FLIP_START, onFlipStartHandler);
page1.addEventListener(PageEvent.FLIP_COMPLETE, onFlipCompleteHandler);

var prevPage:DisplayObject;

function onFlipStartHandler(event:PageEvent):void{
        trace(“Flip Started! “ + event.page.getName() + ” - “ + event.targ);
        setChildIndex(event.targthis.numChildren - 1)
}

function onFlipCompleteHandler(event:PageEvent):void{
        trace(“Flip Completed! “ + event.page.getName() + ” - “ + event.targ);
        prevPage = event.targ;
}

stop();

Simple, and not much going on here, but it’s easy to get a nice transition and we can see something happening! Next is calculating the mouse over the corner, getting a little rollover effect before turning the page, and being able to manually turn the page.

If anyone builds on this example or has one of their own, definitely let me know:)


Here’s the code:

PageFlip AS3 Source Part 1

6 Responses to “PageFlip + Tweener AS3 Part 1”

  1. don Says:

    Donovan, I like your approach to this by using caurinas transition package. I’m extending your work to hopefully catch up to Rubens flex package.

  2. gordee Says:

    Hi there, great work. I was wondering if you had done any more to this regarding mouse Events? I’m new to AS3 and I’m curious to see how its done. Keep up the good work :)

  3. djdonovan Says:

    I actually have a little bit, but not so much with interaction yet. I had fixed the depth issue, but can’t seem to find the files and not sure why I didn’t post it. Must have fallin through the cracks. I’ve removed Tweener as I’ve replaced it with HydroTween in all my work, although I’m using a self contained bezier function with this thing now.

    I do plan on posting an update to this once it’s worthy of doing so.

    With the unit as it is now.. it’s simply using a CLICK event to trigger a bezier curve to animate the position of the fold.

  4. Andy Says:

    Hi, good article, nice1. Interested in seeing any updates? I really do like the page flip effect, but theres very little out there for AS3 Flash CS3. Would love to see your updated version!

    cheers,
    Andy

  5. Ryan Says:

    Hello, so I have everything all set in place but when its published, I am getting errors in the output window of

    ReferenceError: Error #1069: Property _bezier not found on flash.geom.Point and there is no default value.
    at caurina.transitions::Tweener$/::getPropertyValue()
    at caurina.transitions::Tweener$/::updateTweenByIndex()
    at caurina.transitions::Tweener$/::updateTweens()
    at caurina.transitions::Tweener$/onEnterFrame()

  6. djdonovan Says:

    Not seeing this error on my end with the original source. maybe update the tweener package? I don’t use this engine anymore, but it sounds like it just need to be updated.

Leave a Reply