HTML5 Canvas Banner Ads with Greensock Tweening and TypeScript Tutorial
Posted by robert | Filed under TypeScript
Update: New and better Boilerplate that uses ES6 Modules in TypeScript check it out.
How to make a HTML5 Canvas Banner Ad
I got a new project where a client wanted an HTML5 Banner Ad that needed to be under 100k and must be done with HTML5 Canvas. Below is not the banner ad I create for the client just a simple example.
I decided to create my own simple Canvas library (Not sure if you can call this a library) that I could use for Canvas Banner Ads. For the past two weeks I’ve been playing around with TypeScript and love how you can create nice clean code and classes.
One think I didn’t want to create was my own tweening library so I decided to go with the best one I know of. GreenSock Animation Platform (GSAP) also know as TweenMax and TweenLite. TweenLite.js when minified with UglifyJS is 19.7. I also wanted to use a different easing then the default one provided in Tweenlite so I include the EasePack.js which is was only 3.64k after Uglifying.
If you haven’t heard of TweenMax/TweenLite you definitely need to check out what Jack Doyle (The GreenSock guy) has for a tweening library. Check out the TimelineMax also and see what awesome stuff you can do. Also check out the speed test of the TweenLite: Greensock TweenLite Speed Test or http://www.greensock.com/jump-start-js/
Here is the Html5 Canvas Banner Ad: (Disable your Ad blocker to view)
If you are an ActionScript 3 developer this will look familiar to you. At the top we import in our required classes. Then to setup a class in TypeScript we just have to type “class NameOfClass”; Ahh so clean. Next we setup a static and several private properties.
In the constructor you will see a BulkLoader class that you can add objects to preload our images and when they are all loaded a LoaderEvent.LOAD_COMPLETE event/type is dispatched. In the init method you will see we create an Canvas object where we pass in the canvas element id. Next we set up our Bitmap’s which will use the loaded images. Take note of the x, y, alpha, scaleX, scaleY and visible properties that you can set on the Bitmap class. Also take note of how we are adding the Bitmap object as a child to the Canvas object with the method this.canvas.addChild(). There is also a removeChild which will not render the image to the canvas stage.
Finally we get to the TweenLite calls where it takes out Bitmap objects and animates them with ease. That’s it, just preload the image, setup Bitmap classes and use TweenLite/TweenMax to animate the objects around. If you want learn more about the classes please keep reading but you can stop here. You can download the files below.
///<reference path='_declare/greensock.d.ts'/> ///<reference path='_core/display/Canvas.ts'/> ///<reference path='_core/display/Bitmap.ts'/> ///<reference path='_core/utils/BulkLoader.ts'/> ///<reference path='_core/utils/ImageLoader.ts'/> ///<reference path='_core/events/LoaderEvent.ts'/> class BannerAd { static BASE_PATH:string = "assets/images/"; private _canvas:Canvas = null; private _cherry:Bitmap = null; private _cherryDipped:Bitmap = null; private _logo:Bitmap = null; private _boxOfCandy:Bitmap = null; private _bulkLoader:BulkLoader = null; constructor() { this._bulkLoader = new BulkLoader(); this._bulkLoader.addEventListener(LoaderEvent.LOAD_COMPLETE, this.init, this); this._bulkLoader.addFile(new ImageLoader(BannerAd.BASE_PATH + "cherry.png"), "cherry"); this._bulkLoader.addFile(new ImageLoader(BannerAd.BASE_PATH + "cherry-dipped.png"), "cherry-dipped"); this._bulkLoader.addFile(new ImageLoader(BannerAd.BASE_PATH + "logo.png"), "logo"); this._bulkLoader.addFile(new ImageLoader(BannerAd.BASE_PATH + "box.png"), "box"); this._bulkLoader.load(); } private init(event:LoaderEvent):void { this._bulkLoader.removeEventListener(LoaderEvent.LOAD_COMPLETE, this.init); this._canvas = new Canvas("stage"); this._cherry = new Bitmap( this._bulkLoader.getImage("cherry") ); this._cherry.x = 83; this._cherry.y = 3; this._canvas.addChild(this._cherry); this._cherryDipped = new Bitmap( this._bulkLoader.getImage("cherry-dipped") ); this._cherryDipped.x = 83; this._cherryDipped.y = 37; this._cherryDipped.visible = false; this._canvas.addChild(this._cherryDipped); this._logo = new Bitmap( this._bulkLoader.getImage("logo") ); this._logo.x = 222; this._logo.y = 27; this._canvas.addChild(this._logo); this._boxOfCandy = new Bitmap( this._bulkLoader.getImage("box") ); this._boxOfCandy.x = 598; this._boxOfCandy.y = 2; this._boxOfCandy.alpha = 0; this._boxOfCandy.scaleX = 0; this._boxOfCandy.scaleY = 0; this._canvas.addChild(this._boxOfCandy); TweenLite.to(this._boxOfCandy, 1, {delay: 0.5, alpha:1, scaleX:1, scaleY:1, ease:Cubic.easeOut}); TweenLite.to(this._cherry, 0.5, {delay: 1, y:37, ease:Cubic.easeOut, onComplete: this.onCherryComplete.bind(this)}); } private onCherryComplete():void { this._cherryDipped.visible = true; this._canvas.removeChild(this._cherry); TweenLite.to(this._cherryDipped, 0.5, {y:3, ease:Cubic.easeInOut, onComplete: this.onCherryDippedComplete.bind(this)}); } private onCherryDippedComplete():void { TweenLite.to(this._logo, 1, { rotation:720, scaleX:0.5, scaleY:0.5, ease:Bounce.easeOut}); } }
Before we go over the BulkLoader TypeScript class in detail. I want to go over the ImageLoader class. All the ImageLoader class is responsible for it taking and string (Image Path), loading the image and dispatching when it is done. If you look the class extends EventDispatcher so it can dispatch events but the class also implements the IDataStore interface which give us polymorphism. Polymorphism means many forms so if you have several different classes and you want them to be the same in some manner. You would implement an interface. Our BulkLoader class only accepts a IDataStore class. So in the future we can pass in a HtmlLoader or a JsonLoader class and there would be no problems.
///<reference path='../interfaces/IDataStore.ts'/> ///<reference path='../events/EventDispatcher.ts'/> class ImageLoader extends EventDispatcher implements IDataStore { private _image:HTMLImageElement = null; public data:any; public src:string; public complete:bool = false; constructor(path:string) { super(); this.src = path; var self = this; this._image = new Image(); this._image.onload = function() { self.onImageLoad(); } } public load():void { if (this.complete) return; this._image.src = this.src; } private onImageLoad():void { this.complete = true; this.data = this._image; this.dispatchEvent(LoaderEvent.COMPLETE); } }
The BulkLoader class is simple. The purpose for the BulkLoader is to tell other objects when to start loading and it will dispatch a event when all objects have completed loading. The BulkLoader doesn’t care what classes they are but only if they implements the IDataStore interface. The BulkLoader is only concerned with the “complete” property. Every time a IDataStore object finishes loading it will call the onLoadComplete method. Which will loop through the list of loading files and will stop the loop once if finds one that is not complete. If all are complete then the LoaderEvent.LOAD_COMPLETE is dispatched.
///<reference path='../interfaces/IDataStore.ts'/> ///<reference path='../events/EventDispatcher.ts'/> ///<reference path='../events/LoaderEvent.ts'/> class BulkLoader extends EventDispatcher { private _dataStores:IDataStore[] = []; constructor() { super(); this.addEventListener(LoaderEvent.COMPLETE, this.onLoadComplete, this); } public addFile(dataStore:IDataStore, key:string):void { this._dataStores[key] = dataStore; } public getFile(key:string):IDataStore { return this._dataStores[key]; } public getImage(key:string):HTMLImageElement { return this._dataStores[key].data; } public load():void { for (var key in this._dataStores) { var dataStore:IDataStore = this._dataStores[key]; dataStore.addEventListener(LoaderEvent.COMPLETE, this.onLoadComplete, this); dataStore.load(); } } private onLoadComplete(event:LoaderEvent):void { event.target.removeEventListener(LoaderEvent.COMPLETE, this.onLoadComplete); for (var key in this._dataStores) { var dataStore:IDataStore = this._dataStores[key]; if(!dataStore.complete) { return; } } this.dispatchEvent(LoaderEvent.LOAD_COMPLETE); } }
There are several class that I won’t go over right now but the last one I will is the Bitmap class. The Bitmap class accepts an HTMLImageElement object in the constructor that we get from the BulkLoader. We set the image locally and also get the width and height. The render method is being called by the base class (CanvasElement). The base class is also saving the Canvas content before render is called and then restore is called after the render method. We are using Encapsulation here to keep everything nice and clean. It’s a nice way to keep logic behind the scenes so we only have to worry about one piece at a time.
///<reference path='CanvasElement.ts'/> ///<reference path='../utils/NumberUtil.ts'/> class Bitmap extends CanvasElement { private _image:HTMLImageElement = null; public ready:bool = false; constructor(image:HTMLImageElement) { super(); this._image = image; this.width = this._image.width; this.height = this._image.height; } public createChildren():void { } public render():void { this.context.translate(this.x + this.width * 0.5, this.y + this.height * 0.5); this.context.scale(this.scaleX, this.scaleY); this.context.rotate( NumberUtil.degreesToRadians(this.rotation) ); this.context.translate(-this.width * 0.5, -this.height * 0.5); this.context.drawImage(this._image, 0, 0); } }
Please download the source files below and review the code. I hope it is clear what I am doing. If it is not and people want me to explain more I can.
To compile all the TypeScript classes into one JavaScript file I used the command below. Here is the command:
tsc BannerAd.ts -out ../../deploy/app.js
That will save a javascript file called app.js into a folder called deploy two directory up from the BannerAd.ts file.
Also if you like this tutorial please provide a link back to this page or my site.
December 5, 2012 at 7:18 am
Why you are not using the new arrow function on:
this._image.onload = function() {
self.onImageLoad();
}
this._image.onload = e => {
this.onImageLoad();
}
This way you will preserve the scope of the outer execution context and it’s no need to bind the scope of the inner function to a variable “self” predefined outside. BTW some nice articles about TS.
December 5, 2012 at 10:49 am
I haven’t really looked into arrow functions but now that you showed me an example I will start playing with it. Thanks for the tip.
May 4, 2013 at 2:53 pm
Yes these => are a little confusing for me.
I see people define a method signature outside of the constructor.
Public onLoaded: () => void
Then populate that inside the constructor?
This.onLoaded = () => {} sorry my syntax is prob not exact. But if I do this for my class methods, what happens to my getter and setters? If getters reference “this” the class, why does onLoaded need => syntax but the getter does not?
May 5, 2013 at 7:26 am
I totally understand because it was confusing to me too. Take a look at my TypeScript Arrow Function Tutorial.
March 19, 2013 at 11:06 am
Good info. Your covering all the angles. I did want to note though for those who want to just bypass the coding part and visually crate banners visually (as you could do previously in Flash); you should check out Easy WebContent’s Presenter. no coding ; just drag and drop with a full timeline ; http://www.ewcPresenter.com ; it’s free to everyone.