This article shows us a new take on the ‘Flash page flip’ we all love to hate, using HTML5’s canvas and JavaScript. It explains how someone built 20thingsilearned.com
For a long time, web developers leaned on plug-ins to bring truly immersive and rich interactive experiences to their users. HTML5 has begun to change all this by bringing some of the most crucial building blocks of these web augmentations to the open web.F-i.com collaborated with the Google Chrome team on an HTML5-based educational web app called 20 Things I Learned About Browsers and the Web. One of the key ideas behind this project was that it would be best presented in the context of a book. Since the contents of the book is very much about open web technologies we felt it was important to stay true to that by making the container itself an example of what these technologies enable us to accomplish today.
This tutorial will take you through the process of creating your own page flip effect using the canvas element and plenty of JavaScript. Some of the rudimentary code, such as variable declarations and event listener subscription, has been left out of the snippets in this article, so remember to reference the tutorials files for the complete code.
The markup
It’s always important to remember that what we draw on canvas can’t be indexed by search engines, selected by a visitor or found by in-browser searches. For that reason, the content we will be working with is put directly in the DOM and then manipulated by JavaScript if it is available. The markup required for this is minimal:The div has a fixed width and the section is set to hide its overflow. This results in the width of the section acting as a horizontal mask for the div.
The script
The code required to power the page flip is not very complex, but it is quite extensive since it involves a lot of procedurally generated graphics.Let’s start by looking at the description of the constant values we’ll be using throughout the code.
- var BOOK_WIDTH = 830;
- var BOOK_HEIGHT = 260;
- var PAGE_WIDTH = 400;
- var PAGE_HEIGHT = 250;
- var PAGE_Y = ( BOOK_HEIGHT - PAGE_HEIGHT ) / 2;
- var CANVAS_PADDING = 60;
- var book = document.getElementById( "book" );
- var pages = book.getElementsByTagName( "section" );
- for( var i = 0, len = pages.length; i < len; i++ ) {
- pages[i].style.zIndex = len - i;
- flips.push({ progress: 1, target: 1, page: pages[i], dragging: false });
- }
- function mouseMoveHandler( event ) {
- // Offset mouse position so that the top of the spine is 0,0
- mouse.x = event.clientX - book.offsetLeft - ( BOOK_WIDTH / 2 );
- mouse.y = event.clientY - book.offsetTop;
- }
- function mouseDownHandler( event ) {
- if (Math.abs(mouse.x) < PAGE_WIDTH) {
- if (mouse.x < 0 && page - 1 >= 0) {
- flips[page - 1].dragging = true;
- } else if (mouse.x > 0 && page + 1 < flips.length) {
- flips[page].dragging = true;
- }
- }
- // Prevents the text selection cursor from appearing when dragging
- event.preventDefault();
- }
- function mouseUpHandler( event ) {
- for( var i = 0; i < flips.length; i++ ) {
- if( flips[i].dragging ) {
- flips[i].target = mouse.x < 0 ? -1 : 1;
- if( flips[i].target === 1 ) {
- page = page - 1 >= 0 ? page - 1 : page;
- } else {
- page = page + 1 < flips.length ? page + 1 : page;
- }
- }
- flips[i].dragging = false;
- }
- }
In mouseDownHandler we start by checking if the mouse was pressed down on either the left or the right page so that we know which direction we want to start flipping towards. We also ensure that another page exists in that direction since we might be on the first or last page. If a valid flip option is available after these checks, we set the dragging flag of the corresponding flip object to true.
Once we reach the mouseUpHandler we go through all of the flips and check if any of them were flagged as dragging and should now be released. When a flip is released we set its target value to match the side it should flip to depending on the current mouse position. The page number is also updated to reflect this navigation.
The next block of code we are going to cover is inside of the render function, which is called 60 times per second to update and draw the current state of all active flips.
- if( flip.dragging ) {
- flip.target = Math.max( Math.min( mouse.x / PAGE_WIDTH, 1 ), -1 );
- }
- flip.progress += ( flip.target - flip.progress ) * 0.2;
- if( flip.dragging || Math.abs( flip.progress ) < 0.997 ) {
- drawFlip( flip );
- }
If a flip is not in very close range of the book edge, or if it is flagged as dragging, it will be rendered.
Now that all of the logic is in place, we need to draw the graphical representation of a flip depending on its current state. It’s time to look at the first part of the drawFlip(flip) function.
- // Strength of the fold is strongest in the middle of the book
- var strength = 1 - Math.abs( flip.progress );
- // Width of the folded paper
- var foldWidth = ( PAGE_WIDTH * 0.5 ) * ( 1 - flip.progress );
- // X position of the folded paper
- var foldX = PAGE_WIDTH * flip.progress + foldWidth;
- // How far outside of the book the paper is bent due to perspective
- var verticalOutdent = 20 * strength;
- // The maximum width of the left and right side shadows
- var paperShadowWidth = ( PAGE_WIDTH * 0.5 ) * Math.max( Math.min( 1 - flip.progress, 0.5 ), 0 );
- // Mask the page by setting its width to match the foldX
- flip.page.style.width = Math.max(foldX, 0) + "px";
Now that all of the logic is set up, we just have to use the values we have gathered to draw the flip.
- context.save();
- context.translate( CANVAS_PADDING + ( BOOK_WIDTH / 2 ), PAGE_Y + CANVAS_PADDING );
- var foldGradient = context.createLinearGradient(foldX - paperShadowWidth, 0, foldX, 0);
- foldGradient.addColorStop(0.35, '#fafafa');
- foldGradient.addColorStop(0.73, '#eeeeee');
- foldGradient.addColorStop(0.9, '#fafafa');
- foldGradient.addColorStop(1.0, '#e2e2e2');
- context.fillStyle = foldGradient;
- context.strokeStyle = 'rgba(0,0,0,0.06)';
- context.lineWidth = 0.5;
- context.beginPath();
- context.moveTo(foldX, 0);
- context.lineTo(foldX, PAGE_HEIGHT);
- context.quadraticCurveTo(foldX, PAGE_HEIGHT + (verticalOutdent * 2), foldX - foldWidth, PAGE_HEIGHT + verticalOutdent);
- context.lineTo(foldX - foldWidth, -verticalOutdent);
- context.quadraticCurveTo(foldX, -verticalOutdent * 2, foldX, 0);
- context.fill();
- context.stroke();
- context.restore();
All that remains now is drawing the shape of the folded paper using the properties we defined above. The left and right sides of our paper is drawn as straight lines and the top and bottom sides are curved to bring that bent feeling of a folding paper across.
Going further
This is only one example of what can be accomplished by utilising HTML5 features such as the canvas element.I recommend you have a look at the more refined book experience from which this technique is an excerpt, 20 Things I Learned About Browsers and the Web: check it out at www.20thingsilearned.com.
No comments:
Post a Comment