Demo
Download
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
04 | < meta charset = "utf-8" /> |
05 | < title >Making an Instagram Magazine | Tutorialzine Demo</ title > |
08 | < link rel = "stylesheet" href = "assets/css/styles.css" /> |
17 | < div id = "magazine" class = "centerStart" > |
25 | < script src = "assets/js/turn.js" ></ script > |
26 | < script src = "assets/js/script.js" ></ script > |
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
01 | < div id = "page1" class = "page" > |
07 | < span class = "pageNum right" >1 // 32</ span > |
08 | < img src = "assets/img/cover.jpg" alt = "Cover" /> |
12 | < div id = "page2" class = "page" > |
14 | < span class = "pageNum left" >2 // 32</ span > |
19 | < div id = "page3" class = "page" > |
21 | < span class = "pageNum right" >3 // 32</ span > |
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
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
05 | "tags" : [ "beautiful" , "sky" ], |
12 | "created_time" : "1331910134" , |
13 | "link" : "http:\/\/instagr.am\/p\/IPNNknqs84\/" , |
20 | "url" : "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_6.jpg" , |
25 | "url" : "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_5.jpg" , |
29 | "standard_resolution" : { |
30 | "url" : "http:\/\/distilleryimage8.instagram.com\/03c80dd86f7911e1a87612313804ec91_7.jpg" , |
36 | "created_time" : "1331910148" , |
37 | "text" : "Goodnight.\ue056" , |
40 | "profile_picture" : "http:\/\/images.instagram.com\/profiles\/profile_6227738_75sq_1319878922.jpg" , |
44 | "id" : "148395540733414783" |
47 | "id" : "148395420004568888_6227738" , |
51 | "bio" : "Mostly nature pics.\ue32b\ue32b\ue32b Hope you like them.\ue056\ue32a \ue334gi\ue334 " , |
52 | "profile_picture" : "http:\/\/images.instagram.com\/profiles\/profile_6227738_75sq_1319878922.jpg" , |
53 | "full_name" : "jent99" , |
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
02 | $instagramClientID = '-- place your client id key here --' ; |
07 | if ( file_exists ( $cache ) && filemtime ( $cache ) > time() - 60*60){ |
10 | $images = unserialize( file_get_contents ( $cache )); |
16 | $response = file_get_contents ( $api ); |
21 | foreach (json_decode( $response )->data as $item ){ |
26 | $title = mb_substr( $item ->caption->text,0,70, "utf8" ); |
29 | $src = $item ->images->standard_resolution->url; |
32 | "title" => htmlspecialchars( $title ), |
33 | "src" => htmlspecialchars( $src ) |
42 | array_unshift ( $images , array ( "title" => "Cover" , "src" => "assets/img/cover.jpg" )); |
45 | file_put_contents ( $cache ,serialize( $images )); |
49 | $totalPages = count ( $images ); |
50 | foreach ( $images as $i => $image ){ |
54 | <div id= "page<?php echo $i+1?>" class = "page" > |
55 | <div class = "img<?php echo $i+1?>" > |
56 | <span class = "pageNum <?php echo ($i+1)%2? 'right' : 'left'?>" ><?php echo $i +1?> |
57 | <img src= "<?php echo $image['src']?>" alt= "<?php echo $image['title']?>" /> |
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
03 | var mag = $( '#magazine' ); |
10 | mag.bind( 'turned' , function (e, page, pageObj) { |
12 | if (page == 1 && $( this ).data( 'done' )){ |
13 | mag.addClass( 'centerStart' ).removeClass( 'centerEnd' ); |
15 | else if (page == 32 && $( this ).data( 'done' )){ |
16 | mag.addClass( 'centerEnd' ).removeClass( 'centerStart' ); |
19 | mag.removeClass( 'centerStart centerEnd' ); |
24 | setTimeout( function (){ |
30 | $(window).bind( 'keydown' , function (e){ |
37 | else if (e.keyCode==39){ |
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
10 | -moz-transition: 0.3 s left ; |
11 | -webkit-transition: 0.3 s left ; |
18 | background-color : #ccc ; |
39 | .centerStart .turn-page-wrapper:first-child{ |
40 | box-shadow: 0 0 10px #040404 ; |
46 | background-color : rgba( 0 , 0 , 0 , 0.3 ); |
48 | box-shadow: 0 0 3px rgba( 0 , 0 , 0 , 0.25 ); |
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.