Making a fancy book using HTML5 canvases




HTML5 brings us the amazing canvas element. The canvas element is useful for so many things, but here we will look at how to create a book using a canvas and a few other elements.

Technologies used:

Demo

Check out the demo here.

Libraries used in this demonstration:

  • jQuery – not necesarily needed, but speeds things up a bit. Very popular library.
  • Context Blender – recreates some photoshop-like blending with canvases. This is used to overlay our textures on the books.

I also encourage the use of LESS to help speed up CSS development times.

Browser support:

  • All HTML5 browsers should support this no problem.

Step 1: Create a ‘bookshelf’ to store our books

empty-bookcase

Once we have the necessary requirements above, we’ll create ourselves a ‘bookshelf’. This will just be a simple container div with some simple styling:

<div id="bookcase-cont">
<div id="bookcase"></div>
</div>
#bookcase-cont {
height: 390px;
width: 100%;
background-image: url('border.png');
}
#bookcase {
width: 100%;
height: 360px;
background-image: url('wood.png');
box-shadow: inset 0 0 60px 60px rgba(0,0,0,0.4);
}

I’ll walk through this code, though it is fairly simple. We have our two divs, a container and an inner (bookcase-cont and bookcase respectively). Both are set to 100% width, but one is slightly taller than the other.

We then set each a repeating wood effect background. I just searched Google images for a repeating wood texture, but feel free to use whatever you wish. I rotated one and made it slightly smaller for the ‘shelf’ texture. You can find these textures at the bottom of the post.

We then have a nice inset box shadow on our inner div to give it a nice atmospheric feel.

Step 2: Create our texture canvases

I’m going to do this next bit entirely in Javascript, as it will allow us to easily add textures should we want to. I’ll be creating two different textures, a canvas texture and a leather texture.

We’re going to loop over an array of texture images and create a canvas for each one. You need to use texture images that will be able to span the entire length and width of your books, so try and find something big (or you can do a repeating texture). You can also find the textures used here at the bottom of the post.

First, we need to add a ‘hidden’ container to place our texture images. We do this so that we can insert our images into the DOM and get their height and width, otherwise, we won’t be able to tell the size of our images. This is as simple as:

<div id="hidden-container"></div>
#hidden-container {
position: absolute;
left: -9999px;
}

Once we have that in place, we set up two objects, one to contain our images and one to contain our canvases. We have two textures so we need to image elements. I’ve set the property name to the texture name to help us later on.

// An object to store our images
var imgs = {
leather: new Image(),
canvas: new Image()
};
// An object to store our canvases
var canvases = {};

We then loop over each of these elements using jQuery’s super handy &.each() function.

// Loop over each of the imgs and insert it into the hidden container.
$.each(imgs, function(index, img){
$('#hidden-container').append(img);
// Once the image is loaded we create a canvas the exact size of our images
img.onload = function() {
canvases[index] = $("<canvas />");
canvases[index]
.attr('width', img.clientWidth)   // Set the height of the canvas to the img height
.attr('height', img.clientHeight) // same with width.
.attr('id', index)                // Give it an id according to our texture
.addClass('texture');             // And a class of 'texture'
$('body').append(canvases[index]);    // Append it to our document body
// We then draw the image on our canvas
canvases[index].get(0).getContext('2d').drawImage(img, 0, 0);
}
});

I’ll just briefly go over this. jQuery’s $.each() is a generic iterator used to iterate over an object or array. Here we are iterating over the img object. The two variables inside the callback function relate to the property name and property value (index and img, respectively).

On each image, we append the image to our hidden container and attach an onLoad event handler. Once the image has loaded, we create a canvas in our canvases object and set the width, height, id and texture to the values shown. We can then add our canvases to the document body and draw the image on the canvas.

We can then set the src’s of each of our images:

// And here we set the src attribute of each image
imgs.leather.src = 'img/leather.png';
imgs.canvas.src = 'img/canvas.png';

Once you’ve done this, we can wrap it all in jQuery’s domready function (shown here using the shorthand notation). I’ve also added a counter so that we can tell when all the images have been loaded. You should end up with something like this:

$(function(){
// An object to store our images
var imgs = {
leather: new Image(),
canvas: new Image()
};
// An object to store our canvases
var canvases = {};
// Counts how many images are loaded.
var imgsLoaded = 0;
// Loop over each of the imgs and insert it into the hidden container.
$.each(imgs, function(index, img){
$('#hidden-container').append(img);
// Once the image is loaded we create a canvas the exact size of our images
img.onload = function() {
canvases[index] = $("<canvas />");
canvases[index]
.attr('width', img.clientWidth)   // Set the height of the canvas to the img height
.attr('height', img.clientHeight) // same with width.
.attr('id', index)                // Give it an id according to our texture
.addClass('texture');             // And a class of 'texture'
$('body').append(canvases[index]);    // Append it to our document body
// We then draw the image on our canvas
canvases[index].get(0).getContext('2d').drawImage(img, 0, 0);
imgsLoaded++;
if(imgsLoaded === Object.keys(imgs).length){
// Do something
}
}
});
// And here we set the src attribute of each image
imgs.leather.src = 'img/leather.png';
imgs.canvas.src = 'img/canvas.png';
});

After all this,  you should see something roughly like below:

canvas-textures

 

And our texture canvases are all complete.

 Step 3: Creating a function to make a book

Here we will create a simple generating function to make a simple book. We will create a function that accepts one parameter, an object containing all of our book specifications. We start off by creating our function and using jQuery’s $.extend() function to pass a bunch of default options to make sure we always have everything we need to create a book.

function makeBook (options) {
// We need a texture to apply to the book.
if(typeof options.texture === "undefined") return false;
// Our default options
options = $.extend(true,
{
text: "",
color: "#B8293B",
height: 200,
width: 32
}, options);
}

We can then create our book. We create a div with the class ‘book’ and set it’s height and width properties. We also create a span element with the class ‘booktext’ and insert our ‘text’ into it.

To position our book correctly so that it sits on the bookshelf, we will need to offset the top by the height of our container minus the height of our book. This is made very easy by jQuery:

// This gets our container height
var contHeight = $('#bookcase').height();
// Here we create our text and book elements.
var $text = $('<span class="booktext">' + options.text + '</span>');
var $book = $('<div class="book" />');
// And we set the book properties as set in options.
$book.height(options.height);
$book.width(options.width);
// Here we use jQuery $.offset() to position our book resting on the 'shelf'
$book.offset({top: contHeight - options.height});

Now, we will create our book texture by creating a canvas. We then fill the canvas with our colour option and overlay the texture using the context blender.

    // Now we create our book texture
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// We set the properties of our canvas according to our options
canvas.className = 'canvas-texture';
canvas.width = options.width;
canvas.height = options.height;
context.fillStyle = options.color;
// And then we append it to our book
$book.append(canvas);
// Fill with a basic colour
context.fillRect(0, 0, options.width, options.height);
// Overlay our texture onto the book.
options.texture.get(0).getContext('2d').blendOnto(context,'overlay');

Now, we wrap all this up in our finished function and return the book.

function makeBook (options) {
// We need a texture to apply to the book.
if(typeof options.texture === "undefined") return false;
// Our default options
options = $.extend(true,
{
text: "",
color: "#B8293B",
height: 200,
width: 32
}, options);
// This gets our container height
var contHeight = $('#bookcase').height();
// Here we create our text and book elements.
var $text = $('<span class="booktext">' + options.text + '</span>');
var $book = $('<div class="book" />');
// And we set the book properties as set in options.
$book.height(options.height);
$book.width(options.width);
// Here we use jQuery $.offset() to position our book resting on the 'shelf'
$book.offset({top: contHeight - options.height});
// Now we create our book texture
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// We set the properties of our canvas according to our options
canvas.className = 'canvas-texture';
canvas.width = options.width;
canvas.height = options.height;
context.fillStyle = options.color;
// And then we append it to our book
$book.append(canvas);
// Fill with a basic colour
context.fillRect(0, 0, options.width, options.height);
// Overlay our texture onto the book.
options.texture.get(0).getContext('2d').blendOnto(context,'overlay');
// Add our text to the book.
$book.append($text);
// Return our book (note this is a jQuery object!)
return $book;
}

Once we’ve done this, we can start making books!

Step 4: Make a book

Going back to our code from step 2, we’ll insert the code to make a book and insert it into our bookcase.

            if(imgsLoaded === Object.keys(imgs).length){
// Do something
var $book = makeBook({text: "Hello", texture: canvases['leather']});
$('#bookcase').append($book);
}

With all luck, you should see something like this:

book-first-try

Well…the texture worked! But now we need to style our book so that it sits on the bottom of our shelf and the text appears rotated and give it some colour.

.book {
border-radius: 4px;
position: relative;
overflow: hidden;
float: left;
}
.book .canvas-texture {
position: absolute;
}
.book .booktext {
white-space: nowrap;
color: #BBA217;
font-size: 1.25em;
font-family: Garamond, Serif;
font-weight: 900;
text-decoration: none;
text-shadow: 1px 1px 1px rgba(255,255,255,0.2), -1px -1px 1px rgba(0,0,0,0.8);
display: block;
position: relative;
left: 50%;
@originx: 0%;
@originy: 50%;
transform-origin: @originx @originy;
-webkit-transform-origin: @originx @originy;
-moz-transform-origin: @originx @originy;
-o-transform-origin: @originx @originy;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
}

Using the above code should make your book appear a bit prettier. I’ll walk through the CSS briefly and point out the important bits.

.book {
border-radius: 4px;
position: relative;
overflow: hidden;
float: left;
}

in .book we give the thing some rounded edges, as well as setting the position to relative. This allows our offset from earlier to work, sitting our book on the shelf. We also set overflow to hidden to stop anything leaking over the edges. Last, we float it left so that our books sit neatly in a row.

.book .canvas-texture {
position: absolute;
}

We give our canvas texture absolute positioning so that it will sit nicely behind our text not taking up any space.

.book .booktext {
white-space: nowrap;
color: #BBA217;
font-size: 1.25em;
font-family: Garamond, Serif;
font-weight: 900;
text-decoration: none;
text-shadow: 1px 1px 1px rgba(255,255,255,0.2), -1px -1px 1px rgba(0,0,0,0.8);
display: block;
position: relative;
left: 50%;
transform-origin: 0% 50%;
-webkit-transform-origin: 0% 50%;
-moz-transform-origin: 0% 50%;
-o-transform-origin: 0% 50%;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
}

Now this is a bit more involved, but don’t be put off. The top portion just styles our text. I made mine gold with a fancy font and gave it some relief using text-shadow. This makes it look like it’s been embossed, like on a fancy book.

The second portion makes our span a block element, positions it relatively and moves it from the left by 50%. This will become important in a minute when we rotate the text.

In the third portion we rotate our text by 90 degrees, making sure to set the origin of our transformation to the middle-left part of the text. This ensures that our book text is at the top once rotated. By moving the text from the left by 50% earlier, it centres our text horizontally within the book.

With all the luck in the world, you should end up with something like this:

book-second-try

Excellent!

Step 5: Make some more books!

Now we’ve successfully made our first book, let’s create a whole bunch more!

Instead of using makeBook once our images have finished loading, we will create a function that iterates over a list of books that we shall pass to it and adds them to the bookshelf.

This is fairly simple and shouldn’t need to much elaboration:

function addBooks (books) {
$.each(books, function(i, book) {
$('#bookcase').append(makeBook(book));
});
}

and now we can add many books at once by just creating an array of book specifications and calling the addBooks function once our images are all loaded:

            if(imgsLoaded === Object.keys(imgs).length){
var books = [
{ text: 'Hello', height: 320, width: 40, color: '#327', texture: canvases.leather },
{ text: 'World', texture: canvases.canvas }
];
addBooks(books);
}

Simple! The code above should give you something like this:

A Bookcase with Books

 

Step 6: Extend, extend, extend

A Full Bookcase

I’ve used the basic code above and made the books into links for my custom Chrome homepage. I added a bit more of a classy look to the books by applying the basic colour as a gradient to make them look more 3D. I also added some horizontal gold strips for an extra classy look.

Finally, I also added CSS hover transitions to the book to make it look like they are being pulled off the shelf when hovered over.

Conclusion

We’ve successfully used a combination of technologies to make some really realistic looking books and a nice looking bookshelf. We’ve also used a library from GitHub with success to combine some canvas elements. This lesson helps our understanding of how canvases work and how to create helpful javascript functions in our websites.

Overall, looking pretty good. In a future post I will go over how we can add books dynamically using HTML5 localStorage so that you can add books on the fly.

Check out the demo here.

 Images

 

Leave a Reply