Making a Page Flip Magazine with turn.js

Making a Page Flip Magazine with turn.js

jQuery Trickshots is our new epic jQuery tips and tricks book. Check it out!

The page flip effect used to be the quintessential Flash animation. On the web, it has powered everything from magazines to presentations, with its popularity declining over time, only to be reinvented on mobile devices as ebook reading apps.

In this tutorial we are going to use PHP and the turn.js plugin, an implementation of the page flip effect with pure CSS3 and jQuery, to build a pretty magazine. We will fetch the most popular images from Instagram every hour, and use them as pages.

HTML

First we need to lay down the foundations of today’s example. We will use a single page design, which combines HTML5 markup and PHP in the same file for greater simplicity. You can see the resulting layout below:

index.php

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Making an Instagram Magazine | Tutorialzine Demo</title>

        <!-- Our Stylesheet -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

		<div id="magazine" class="centerStart">

		<!-- PHP will go here -->

		</div>

        <!-- JavaScript includes - jQuery, turn.js and our own script.js -->
		<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
		<script src="assets/js/turn.js"></script>
		<script src="assets/js/script.js"></script>

    </body>
</html>

We include styles.css in the head, and our JavaScript files at the bottom. The latter are the jQuery library, the turn.js plugin and script.js, where we will be initializing the plugin and listening for keyboard events. The PHP code that we will be writing in the next section will go in the #magazine div. PHP will have the job of generating the pages of our magazine, which will be used by turn.js.

As an example, here is the markup of the first three pages of the magazine:

Generated code

<div id="page1" class="page">
	<div class="img1">

		<!-- The pageNum span can be either on the left,
				or the right if the page is odd/even. -->

		<span class="pageNum right">1 // 32</span>
		<img src="assets/img/cover.jpg" alt="Cover" />
	</div>
</div>

<div id="page2" class="page">
	<div class="img2">
		<span class="pageNum left">2 // 32</span>
		<img src="http://distilleryimage7.instagram.com/..." alt="Little tulips" />
	</div>
</div>

<div id="page3" class="page">
	<div class="img3">
		<span class="pageNum right">3 // 32</span>
		<img src="http://distilleryimage2.instagram.com/..." alt="My style" />
	</div>
</div>

The divs you see above are direct descendants of the #magazine div. This is the only requirement imposed by turn.js. You don’t need to have any special classes or data attributes for the elements to be interpreted as pages. With this we are ready to move on with the PHP code!

Page Flip Magazine with CSS3 and jQuery

Page Flip Magazine with CSS3 and jQuery

PHP

PHP will have the task of communicating with Instagram’s API, caching the results, and generating the markup you saw above.

The first step is to register at the Instagram developer website. After you obtain your client_id key, place it in index.php as the value of $instagramClientID. We won’t be needing any of the advanced functionality of the API, we will only be requesting the most popular images. This frees us from having to implement OAuth authentication, which would make today’s example significantly more complex.

Note that if you want to modify the magazine and show photos other than the most popular, say your latest images, you will need to implement OAuth and authenticate your app to have access to your photos. Consult the docs for further information.

Here is an example JSON response of the currently popular images on Instagram. I have omitted some of the attributes to make the code easier to read.

Popular images JSON response

{    "meta": {
        "code": 200
    },
    "data": [{
        "tags": ["beautiful", "sky"],
        "location": "null",
        "comments": {
            "count": 31,
            "data": [...]
        },
        "filter": "Normal",
        "created_time": "1331910134",
        "link": "http:\/\/instagr.am\/p\/IPNNknqs84\/",
        "likes": {
            "count": 391,
            "data": [..]
        },
        "images": {
            "low_resolution": {
                "url": "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_6.jpg",
                "width": 306,
                "height": 306
            },
            "thumbnail": {
                "url": "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_5.jpg",
                "width": 150,
                "height": 150
            },
            "standard_resolution": {
                "url": "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_7.jpg",
                "width": 612,
                "height": 612
            }
        },
        "caption": {
            "created_time": "1331910148",
            "text": "Goodnight.\ue056",
            "from": {
                "username": "jent99",
                "profile_picture": "http:\/\/images.instagram.com\/profiles\/profile_6227738_75sq_1319878922.jpg",
                "id": "6227738",
                "full_name": "jent99"
            },
            "id": "148395540733414783"
        },
        "type": "image",
        "id": "148395420004568888_6227738",
        "user": {
            "username": "jent99",
            "website": "",
            "bio": "Mostly nature pics.\ue32b\ue32b\ue32b Hope you like them.\ue056\ue32a     \ue334gi\ue334                    ",
            "profile_picture": "http:\/\/images.instagram.com\/profiles\/profile_6227738_75sq_1319878922.jpg",
            "full_name": "jent99",
            "id": "6227738"
        }
    }, {
		/* More photos here*/
	}]
}

The API is limited to returning only 32 pics, but this is plenty for our example. You can see that each photo has three image sizes, but we will only be needing the standard one. There is also various other information that you can use like caption, dimensions, tags, comments, and more.

PHP will cache the results of this API call so we hit Instagram’s servers only once per hour. This will make our application more responsive and limit the number of calls.

index.php

// You can obtain this client ID from the Instagram API page
$instagramClientID = '-- place your client id key here --';

$api = 'https://api.instagram.com/v1/media/popular?client_id='.$instagramClientID;
$cache = 'cache.txt';

if(file_exists($cache) && filemtime($cache) > time() - 60*60){
	// If a cache file exists, and it is
	// fresher than 1 hour, use it
	$images = unserialize(file_get_contents($cache));
}
else{
	// Make an API request and create the cache file

	// Fetch the 32 most popular images on Instagram
	$response = file_get_contents($api);

	$images = array();

	// Decode the response and build an array
	foreach(json_decode($response)->data as $item){

		$title = '';

		if($item->caption){
			$title = mb_substr($item->caption->text,0,70,"utf8");
		}

		$src = $item->images->standard_resolution->url;

		$images[] = array(
			"title" => htmlspecialchars($title),
			"src" => htmlspecialchars($src)
		);
	}

	// Remove the last item, so we still have
	// 32 items when when the cover is added
	array_pop($images);

	// Push the cover in the beginning of the array
	array_unshift($images,array("title"=>"Cover", "src"=>"assets/img/cover.jpg"));

	// Update the cache file
	file_put_contents($cache,serialize($images));
}

# Generate the markup
$totalPages = count($images);
foreach($images as $i=>$image){

	?>

	<div id="page<?php echo $i+1?>" class="page">
		<div class="img<?php echo $i+1?>">
			<span class="pageNum <?php echo ($i+1)%2? 'right' : 'left'?>"><?php echo $i+1?> // <?php echo $totalPages?></span>
			<img src="<?php echo $image['src']?>" alt="<?php echo $image['title']?>" />
		</div>
	</div>

	<?php

}

Caching is straightforward: we are using a temporary file – cache.txt – to store a serialized representation of the image array. If the cache file is non-existing or is older than an hour, we issue a new API request.

Notice the calls to array_pop and array_unshift. These are necessary as to make room for the custom cover image that is stored in assets/img. The magazine works best if we have an even number of pages, otherwise we would be unable to turn the last one, which would feel unnatural.

We are now ready for the plugin!

jQuery

Using turn.js is really simple. As we already have the markup of the magazine, we just need to call the turn() method. While we are at it, we will also listen for presses on the arrow keys, which will trigger page transitions.

assets/js/script.js

$(function(){

	var mag = $('#magazine');

	// initiazlie turn.js on the #magazine div
	mag.turn();

	// turn.js defines its own events. We are listening
	// for the turned event so we can center the magazine
	mag.bind('turned', function(e, page, pageObj) {

		if(page == 1 && $(this).data('done')){
			mag.addClass('centerStart').removeClass('centerEnd');
		}
		else if (page == 32 && $(this).data('done')){
			mag.addClass('centerEnd').removeClass('centerStart');
		}
		else {
			mag.removeClass('centerStart centerEnd');
		}

	});

	setTimeout(function(){
		// Leave some time for the plugin to
		// initialize, then show the magazine
		mag.fadeTo(500,1);
	},1000);

	$(window).bind('keydown', function(e){

		// listen for arrow keys

		if (e.keyCode == 37){
			mag.turn('previous');
		}
		else if (e.keyCode==39){
			mag.turn('next');
		}

	});

});

You can read more about what events the plugin emits and how to use them, in the turn.js reference.

Now let’s make it pretty!

CSS

We need to set explicit dimensions of the magazine and style the pages and page numbers. turn.js will handle the rest.

assets/css/styles.css

#magazine{
	width:1040px;
	height:520px;
	margin:0 auto;
	position:relative;
	left:0;

	opacity:0;

	-moz-transition:0.3s left;
	-webkit-transition:0.3s left;
	transition:0.3s left;
}

#magazine .page{
	width:520px;
	height:520px;
	background-color:#ccc;
	overflow:hidden;
}

/* Center the magazine when the cover is shown */
#magazine.centerStart{
	left:-260px;
}

/* Center the magazine when the last page is shown */
#magazine.centerEnd{
	left:260px;
}

.page img{
	height:520px;
	width:520px;
	display:block;
}

/* Show a dark shadow when the cover is shown */
.centerStart .turn-page-wrapper:first-child{
	box-shadow:0 0 10px #040404;
}

/* Page Numbers */

span.pageNum{
    background-color: rgba(0, 0, 0, 0.3);
    bottom: 25px;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.25);
    color: #FFFFFF;
    font-size: 11px;
    height: 24px;
    line-height: 22px;
    opacity: 0.9;
    position: absolute;
    text-align: center;
    width: 55px;
}

span.pageNum.left{
	left:0;
	right:auto;
}

span.pageNum.right{
	left:auto;
	right:0;
}

/* Hide the page number on the cover */
#page1 .pageNum{
	display:none;
}

With this our magazine is complete!

We’re done!

This example works in all recent browsers – Firefox, Chrome, Safari, Opera and even IE. It is even usable on iOS and Android. You can use this effect as part of photo galleries, templates or even real magazines. However you will have to create a fallback version for older browsers, which don’t have what it takes to display it properly.

by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and publishes new tutorials weekly.

Tutorials

75 Comments

  1. war says:

    Is beatiful simple , congratulations martin! this is amazing

  2. Juca says:

    Really cool man =)

  3. Lam says:

    Works great! It looks really nice on iPad.

  4. Max Requiao says:

    very good tutorial, the page is gorgeous

  5. To bad this does not have a IE7-9 fallback to a simple flip, but I think is not that hard with this as a base! I always used a flash alternative for this type of effect. Great tutorial! I

    1. ellen says:

      can you tell us, how we can do that, (flash alternative?)i like this flip page very much ,but IE7 mmmm,thank you ,ellen

  6. efendi says:

    how does it works without instagram-api only with local stored images?

    thanks

    1. Martin Angelov says:

      It would be even easier - get all the images from a folder with the glob PHP function like this: glob('folder/*.jpg'). Then loop the array and output the same markup that you see from lines 54 to 59 in the PHP section of the tutorial.

      1. YuckyMuffin says:

        What is the exact code for local images? (By the way, awesome tutorial!)

      2. Lucille says:

        Amazing tutorial!

        I've tried implementing the glob function, but with no luck.

        Could you please specify a little bit more on how to get this working using local stored images?

        Also, is it possible to do this using other file formats, instead of .jpg? For instance, PDF documents, and other?

        Thank you for your input.

        1. Daniel says:

          I´ve tried too.
          Have you the code for local pictures?

          thanks,daniel

  7. K. Denson says:

    Very nice. I really like what you did. I'll be messing around with turn.js in a few days so nice to know how simple it is to work with. As mentioned too bad that there's no fall back, but I guess I'll figure out something...

  8. Another Awesome Stuff !!! Thanks for sharing!!!

  9. Ray Piñoco says:

    Wow, great flip animation..

    Is it possible to replicate <a href="http://bit.ly/zGbm8U" target="_blank">Flipboard</a> effect using turn js? The one-page-flip-effect, i mean which uses CSS3 Transform features. I'm having hard time tweaking something like <a href="http://bit.ly/x94yIv" target="_blank">this</a>.

    And yes, don't worry about Firefox or Opera. I'm concern in Webkit browsers only. I wish someone can provide me a link how to do this..

    Thanks.

    1. Martin Angelov says:

      Turn.js won't help you with that, but I think it would be possible using the technique from the apple login form tutorial from a few weeks back.

  10. This is cool, Martin. Awesome flip effect and nicely suited for pad-devices. Thanks for sharing.

  11. Adam says:

    How can we implement to show only images from a user. I tried using https://api.instagram.com/v1/users/USERID/media/recent?client_id=, but I assume I am missing much more. This is still great, just hoping to be able to show my photos instead of the most popular.

    1. Martin Angelov says:

      You will need to authenticate the user and send an auth token with the request. It would be best to use a library for this. Read through instagram's developer documentation once you register.

      1. Harry says:

        Could you retrieve the images from a flickr feed or tunblr, then u could just tick the corresponding sharing tab in instagram when u want to post ur image to the magazine?

    2. Avi says:

      @Adam, use this tutorial: http://www.9lessons.info/2012/05/login-with-instagram-php.html and combine it with Martin's amazingness to get what you're looking for. I did it on http://www.flipyeah.com.

      Basically, you want https://api.instagram.com/v1/users/self/media/recent?access_token=[ACCESS TOKEN] to have a logged in user's flipbook display. That [ACCESS TOKEN] issue is solved with the 9lessons's tutorial (in my case, I have access_token='.$token.

      Good luck!

      1. Avi says:

        I also just changed the $cache line to

        $cache = 'cache_'.$tokenz.'.txt';

        so that each visitor gets a unique cache file. This way users should not see each others' photos.

        1. Dmitry says:

          Avi, thanks. I was able to implement a similar site

  12. Chris says:

    Pretty amazing! Though it really needs an IE7 / IE8 CSS/JS fallback that works.

  13. Simon says:

    Great tutorial, can be adapted to work with other image API's like Flickr etc.

    One quick question.. loading all those images at once can really slow down page load times, espicially if you plan to show more than 32!

    Would it be possible to build in some kind of ajax request that pulled in the nearest two or three images and updated on the page turn?

    Great tutorial.

    1. Martin Angelov says:

      Thank you Simon!

      Yes, you can do this. The easiest way would be to populate the pages of the magazine with a generic "loading image" graphic and add code to the turned event (line 10 of script.js) which changes the src attribute of the generic images with the real ones for the next 2 pages.

  14. Frank says:

    This is grt Martin. Thx

  15. Mark says:

    Whoa. I see a lot a JavaScript that is not fully functional on a tablet or phone, but this is one that works on everything. I would love to try this on an iPad.

  16. Simon says:

    This is awesome!!!!! :D

  17. angelo says:

    how can i use local pictures, what i mean is i will use my pictures on my webhosting. thanks

    1. Martin Angelov says:

      See the answer I gave to efendi above.

  18. Nicole says:

    Would it be possible to add a link that would jump to a specific page in the magazine? If so, how would I implement that?

    1. Martin Angelov says:

      turn.js has support for this. You can use this code to turn to a specific page:

      $('#magazine').turn('page',3);
      
  19. Anonymous says:

    Great tutorial! But doesn't work in IE 8 and older. Bummer!

  20. samir says:

    hi , thank you for this great tutorial that was great you are very smart , can you explain in simple steps how this algorithm works please , for example step 1 php code populate and build the whole album from the instagram api , next all what the jquery do is giving the flip functionality to the final populated album , please explain how this code work step by step in easy to understand way in simple words without code at all first , thanks.

  21. Bruce King says:

    Yep this is cool, but not good for IE8.

  22. Deeds says:

    Don't know what to say... Very nice!

  23. Akash says:

    I am using this jQuery in 'single' mode.
    At the time of page turn it shows gray part rather than the privious image.
    It shows correctly in 'double' mode.
    How can I solve this?

    1. henry says:

      Hi Akash,

      I am having the same problem as you. Did you ever solve this?

      Henry

  24. e11world says:

    This is great!!! I know this may seem like a question that's been answered before but how can I load images from Google Picasa Web or Flickr?

  25. Really awesome job...

  26. Eduardo says:

    Can i use it without PHP?

    Like using with local images?

    Thanks!

  27. Chris says:

    This script is shown on this clip from the BBC http://www.bbc.co.uk/news/business-17946128

    1. Martin Angelov says:

      Cool!

  28. Kim says:

    This is a great alternative to the 'regular' Flash Pageflip Magazines. Thank you!

    We placed a link to this tutorial on our Dutch Webdevelopment website.

  29. Raj says:

    Awesome... this one is simply superb and out of the box... good work...

  30. Joris says:

    Thanks, this is awesome. It works great on iPad. Unfortunately, the Galaxy Tab browser doesn't support it very well...

  31. Reen says:

    Good work!!!

    Maybe i used it for my next project!

    Cheers

  32. Aadil says:

    Great code and awesome design.

  33. Eric says:

    The edges a bit pixelated. Is there a way to improve that?

    Other than that, it's great :)

  34. mava says:

    Great stuff!!! I'm a newbie with css 3 and html5, I'm trying to implement box-shadow on the last page but can't seem to get it right

  35. Natasha says:

    Awesome tutorial! Thanks so much for sharing. :)

  36. Umar says:

    Very nice script .. but why last page not coming to center automatically like first one?

  37. Joey says:

    How would you do that without PHP? I just want to load local images instead.

  38. Patrik says:

    Good day,

    I have larger pictures on my website and for smaller monitors missing scrollbar on the right side. Where is a problem?

    Thank you

  39. Daniel says:

    Thanks for your tut....it´s really great!

    But i have some problems with the local pictures.
    I have read your replay to effendi, but it doesn´t work.

    can you help me?

    thanks,
    daniel

  40. Turnjs updated their commercial version. Thanks for this great tuts.

  41. Connor says:

    Is it possible to do a flip magazine using your own instagram images by taking them off of instagram or possibly make a flip magazine out of the pictures I liked on instagram?

  42. Sara says:

    How do you do man
    I want to thank you about this amazing tutorials and I have question about this tutorial
    so if I want to flip the pages on right not on left when page is loaded like arabic book I mean I hope you will give me an answer
    again thank you

    Good luck

  43. shumaila says:

    I want to add page turn on click of any link . Please guide me

  44. edyz gio says:

    Wow..... Cool Man....

  45. kidloco says:

    Hi,
    I've tried with the zip example, pasted client ID in the proper place but it's not working, i just see front cover but its not clickable? Does anybody know what could be the problem? Thanks!

  46. Saroj Shahi says:

    Hi Martin,

    I can't seem to find out why mine is not working.
    I got the client ID as supposed to and replaced it.

    However, nothing is happening.

    http://gallery.sarojshahi.com.np/

    Help Please ??

  47. Candice says:

    Hi there!

    I'm really LOVING this, but don't want to have to connect to the instagram API. I'd like to just add my own content to each page. Like a webpage that makes use of the page turning functionality. Is that possible?

    If so, PLEASE let me know as I would love to use this functionality for an assignment. Would be of great help. Thank you :)

  48. Cubixsyntax says:

    Any script how to make button events like:
    - turn to page 5
    - zoom
    - print , etc

    any way i could do this? please help. thanks!

  49. hansengine says:

    Hi, i have a question.

    Is possible add previous and next buttons?, How can i do this?

    Thanks for your help.

  50. IraMaura says:

    hi Martin!

    Any ideas on how to implement this fantastic feature into a Drupal website?

    Many thanks in advance.

  51. Alexey says:

    Great tut mate !!!

    It is possible to add zoom and navigation ?

  52. I am about to use in my website. thanks for post.

  53. mi linda says:

    could you help me how to make it auto flip without dragging. Thanks Martin

    with Respect!

  54. hissyfit says:

    Why is the last page not centered.
    What is missing.

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links