Skip to content

Categories:

Making a Page Flip Magazine with turn.js

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



    
        
        Making an Instagram Magazine ,
    "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.ue32bue32bue32b Hope you like them.ue056ue32a     ue334giue334                    ",
            "profile_picture": "http://images.instagram.com/profiles/profile_6227738_75sq_1319878922.jpg",
            "full_name": "jent99",
            "id": "6227738"
        
    }, 
		/* More photos here*/
	]
}</pre>
<p>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.</p>
<p>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.</p>
<h4>index.php</h4>
<pre class="brush: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

</pre>
<p>Caching is straightforward: we are using a temporary file – <em>cache.txt</em> – 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.</p>
<p>Notice the calls to <em><strong>array_pop</strong></em> and <em><strong>array_unshift</strong></em>. These are necessary as to make room for the custom cover image that is stored in <span style="text-decoration: underline;"><em>assets/img</em></span>. 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.</p>
<p>We are now ready for the plugin!</p>
<h3>jQuery</h3>
<p>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.</p>
<h4>assets/js/script.js</h4>
<pre class="brush: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');
		

	});

});</pre>
<p>You can read more about what events the plugin emits and how to use them, in the <a href="https://github.com/blasten/turn.js/wiki/Reference" target="_blank">turn.js reference</a>.</p>
<p>Now let’s make it pretty!</p>
<h3>CSS</h3>
<p>We need to set explicit dimensions of the magazine and style the pages and page numbers. turn.js will handle the rest.</p>
<h4>assets/css/styles.css</h4>
<pre class="brush: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;
</pre>
<p>With this our magazine is complete!</p>
<h3>We’re done!</h3>
<p>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.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?i=DoO1_uCekps:GyZpbKQ-jn4:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?i=DoO1_uCekps:GyZpbKQ-jn4:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?i=DoO1_uCekps:GyZpbKQ-jn4:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?i=DoO1_uCekps:GyZpbKQ-jn4:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Tutorialzine?a=DoO1_uCekps:GyZpbKQ-jn4:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/Tutorialzine?d=qj6IDK7rITs" border="0"></img></a>
</div>
<p><img src="http://feeds.feedburner.com/~r/Tutorialzine/~4/DoO1_uCekps" height="1" width="1" />|
<div><a href="http://tutorialzine.com/2012/03/instagram-magazine-php-jquery/"><img src="http://cdn.tutorialzine.com/img/featured/1889.jpg" /></a></div>
<p> 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 an Instagram powered magazine.}</p>
<p>Read more : <a target="_blank" href="http://feedproxy.google.com/~r/Tutorialzine/~3/DoO1_uCekps/" title="Making a Page Flip Magazine with turn.js">Making a Page Flip Magazine with turn.js</a></p>
		<div class="clear"></div>
	</div><!-- .entry-content-->
	
	<p class="filed categories alt-font tight">Posted in Uncategorized.</p>
	
	<p class="comments-link"><a href="http://developersarena.com/web/2012/03/making-a-page-flip-magazine-with-turn-js/#respond" rev="post-8960" >No comments</a></p>

	<p class="by-line">
		<span class="author vcard full-author">
			<span class="by alt-font">By</span> <a class="url fn" href="http://developersarena.com/author/Martin Angelov/" title="View all posts by Martin Angelov">Martin Angelov</a>		</span>
		<span class="date full-date"><span class="ndash alt-font">–</span> <abbr class="published" title="2012-03-16T19:02">March 16, 2012</abbr></span>
	</p><!--/by-line-->

	<div id="post-comments-8960-target"></div>
	<div class="clear"></div>
	
	</div><!-- .post -->	<div id="comments">

<div class="rule-major"><hr /></div>

<h2 class="h1 comments-title">0 Responses</h2>

<p>Stay in touch with the conversation, subscribe to the <a class="feed" rel="alternate" href="http://developersarena.com/web/2012/03/making-a-page-flip-magazine-with-turn-js/feed/"><acronym title="Really Simple Syndication">RSS</acronym> feed for comments on this post</a>.</p>


<div id="respond-p8960">
	<form action="http://developersarena.com/wp-comments-post.php" method="post" class="comment-form">
		<p class="comment-form-comment tight">
		<label class="h3" for="comment-p8960">Leave a Reply <em id="cancel-comment-reply"><a rel="nofollow" id="cancel-comment-reply-link-p8960" href="/web/2012/03/making-a-page-flip-magazine-with-turn-js/#respond-p8960" style="display:none;">Cancel</a></em></label>
			<br class="lofi" />
			<span class="comment-form-comment-area">
				<textarea id="comment-p8960" name="comment" rows="8" cols="40"></textarea><br />
				<em class="some-html-is-ok"><abbr title="You can use: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> ">Some HTML is OK</abbr></em>
			</span>
		</p>
		<p class="comment-form-user-info tight">
			<input type="text" id="author-p8960" name="author" value="" size="22" />
			<label for="author-p8960">Name <em>(required)</em></label>
		</p><!--/name-->
		<p class="comment-form-user-info tight">
			<input type="text" id="email-p8960" name="email" value="" size="22" />
			<label for="email-p8960">Email  <em>(required, but never shared)</em></label>
		</p><!--/email-->
		<p class="comment-form-user-info tight">
			<input type="text" id="url-p8960" name="url" value="" size="22" />
			<label title="Your website address" for="url-p8960">Web</label>
		</p><!--/url-->
		<p class="tight">
			<input name="submit" type="submit" value="Post comment" />
			<span class="comment-form-trackback">or, reply to this post via <a rel="trackback" href="http://developersarena.com/web/2012/03/making-a-page-flip-magazine-with-turn-js/trackback/">trackback</a>.</span>
		</p>
<input type='hidden' name='comment_post_ID' value='8960' id='comment_post_ID_p8960' />
<input type='hidden' name='comment_parent' id='comment_parent_p8960' value='0' />
	</form>
</div>
	</div><!--#comments-->

	<div class="pagination-single">
		<span class="previous">« <a href="http://developersarena.com/web/2012/03/enrico-zimuel-on-use-agilezen-in-php-with-zf2%e2%80%b3/" rel="prev">Enrico Zimuel on “Use AgileZen in PHP with ZF2″</a></span>
		<span class="next"><a href="http://developersarena.com/web/2012/03/rob-allen-lists-the-zend-framework-events/" rel="next">Rob Allen lists the Zend Framework events</a> »</span>
	</div>

</div><!--#content-->

<hr class="lofi" />
<div id="sidebar">
	<div id="carrington-subscribe" class="widget">
		<h2 class="widget-title">Subscribe</h2>
		<a class="feed alignright" title="RSS 2.0 feed for posts" rel="alternate" href="http://developersarena.com/feed/">
			<img src="http://developersarena.com/wp-content/themes/carrington-blog/img/rss-button.gif" alt="Developers Arena latest posts" title="Developers Arena latest posts" />
		</a>
	</div><!--.widget-->
	<div id="carrington-about" class="widget">
		<div class="about">
			<h2 class="widget-title">About Developers Arena</h2>
Developersarena.com is a place where we share our knowledge, thoughts. DA handles topics such as web technologies, mobile applications, technology, tips etc.<a class="more" href="http://developersarena.com/about/">more →</a>		</div>
	</div><!--.widget-->

	<div id="primary-sidebar">
		<div id="recent-posts-3" class="widget widget_recent_entries">		<h2 class="widget-title">Recent Posts</h2>		<ul>
					<li>
				<a href="http://developersarena.com/web/2019/09/danville-metropolis-council-works-with-casino-12/">Danville Metropolis Council Works with Casino Legalization Effort</a>
						</li>
					<li>
				<a href="http://developersarena.com/web/2019/09/sign-up-for-the-actual-sat-knowing-you-re-in-a-5/">Sign up for the actual SAT knowing you’re in a position to register for taking the test.</a>
						</li>
					<li>
				<a href="http://developersarena.com/web/2019/09/coming-to-america-our-best-pupil-podcasts-around-3/">Coming To America: Our Best Pupil Podcasts Around Immigration</a>
						</li>
					<li>
				<a href="http://developersarena.com/web/2019/09/fine-martial-arts-styles-portfolio-suggestions-11/">Fine Martial arts styles Portfolio Suggestions Applicants may perhaps submit their very own portfolio 1 of 2 ways</a>
						</li>
					<li>
				<a href="http://developersarena.com/web/2019/09/when-must-my-scholar-take-the-posed/">When Must My Scholar Take the POSED?</a>
						</li>
				</ul>
		<div class="clear"></div></div><div id="search-3" class="widget widget_search"><form role="search" method="get" id="searchform" class="searchform" action="http://developersarena.com/">
				<div>
					<label class="screen-reader-text" for="s">Search for:</label>
					<input type="text" value="" name="s" id="s" />
					<input type="submit" id="searchsubmit" value="Search" />
				</div>
			</form><div class="clear"></div></div>	</div><!--#primary-sidebar-->
	<div id="secondary-sidebar">
<div id="categories-3" class="widget widget_categories"><h2 class="widget-title">Categories</h2>		<ul>
<li class="cat-item-none">No categories</li>		</ul>
<div class="clear"></div></div><div id="tag_cloud-3" class="widget widget_tag_cloud"><h2 class="widget-title">Tags</h2><div class="tagcloud"></div>
<div class="clear"></div></div><div id="recent-comments-3" class="widget widget_recent_comments"><h2 class="widget-title">Recent Comments</h2><ul id="recentcomments"></ul><div class="clear"></div></div>	</div><!--#secondary-sidebar-->
	<div class="clear"></div>
</div><!--#sidebar-->			<div class="clear"></div>
			</div><!-- .wrapper -->
		</div><!-- #main -->
		<hr class="lofi" />
		<div id="footer" class="section">
			<div class="wrapper">		
				<p id="generator-link">Proudly powered by <a href="http://wordpress.org/" rel="generator">WordPress</a> and <a href="http://carringtontheme.com" title="Carrington theme for WordPress">Carrington</a>.</p>
				<p id="developer-link"><a href="http://crowdfavorite.com" title="Custom WordPress development, design and backup services." rel="developer designer">Carrington Theme by Crowd Favorite</a></p>
			</div><!--.wrapper-->
		</div><!--#footer -->
	</div><!--#page-->
	<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-19328731-1']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
</body>
</html>