function isCanvasContext(obj) { var ctx2d = typeof CanvasRenderingContext2D !== 'undefined' && obj instanceof CanvasRenderingContext2D; return obj && (ctx2d || isGL(obj)); }
function CanvasApp(render, options) { if (!(this instanceof CanvasApp)) return new CanvasApp(render, options); //allow options to be passed as first argument if (typeof render === 'object' && render) { options = render; render = null; } render = typeof render === 'function' ? render : options.onRender; options = options||{}; options.retina = typeof options.retina === "boolean" ? options.retina : true; var hasWidth = typeof options.width === "number", hasHeight = typeof options.height === "number"; //if either width or height is specified, don't auto-resize to the window... if (hasWidth || hasHeight) options.ignoreResize = true; options.width = hasWidth ? options.width : window.innerWidth; options.height = hasHeight ? options.height : window.innerHeight; var DPR = options.retina ? (window.devicePixelRatio||1) : 1; //setup the canvas var canvas, context, attribs = options.contextAttributes||{}; this.isWebGL = false; //if user provided a context object if (isCanvasContext(options.context)) { context = options.context; canvas = context.canvas; } //otherwise allow for a string to set one up if (!canvas) canvas = options.canvas || document.createElement("canvas"); canvas.width = options.width * DPR; canvas.height = options.height * DPR; if (!context) { if (options.context === "webgl" || options.context === "experimental-webgl") { context = getGL({ canvas: canvas, attributes: attribs }); if (!context) { throw "WebGL Context Not Supported -- try enabling it or using a different browser"; } } else { context = canvas.getContext(options.context||"2d", attribs); } } this.isWebGL = isGL(context); if (options.retina) { canvas.style.width = options.width + 'px'; canvas.style.height = options.height + 'px'; } this.running = false; this.width = options.width; this.height = options.height; this.canvas = canvas; this.context = context; this.onResize = options.onResize; this._DPR = DPR; this._retina = options.retina; this._once = options.once; this._ignoreResize = options.ignoreResize; this._lastFrame = null; this._then = Date.now(); this.maxDeltaTime = typeof options.maxDeltaTime === 'number' ? options.maxDeltaTime : 1000/24; //FPS counter this.fps = 60; this._frames = 0; this._prevTime = this._then; if (!this._ignoreResize) { options.resizeDebounce = typeof options.resizeDebounce === 'number' ? options.resizeDebounce : 50; addEvent(window, "resize", debounce(function() { this.resize(window.innerWidth, window.innerHeight); }.bind(this), options.resizeDebounce, false)); addEvent(window, "orientationchange", function() { this.resize(window.innerWidth, window.innerHeight); }.bind(this)); } if (typeof render === "function") { this.onRender = render.bind(this); } else { //dummy render function this.onRender = function (context, width, height, dt) { }; } this.renderOnce = function() { var now = Date.now(); var dt = Math.min(this.maxDeltaTime, (now-this._then)); this._frames++; if (now > this._prevTime + 1000) { this.fps = Math.round((this._frames * 1000) / (now - this._prevTime)); this._prevTime = now; this._frames = 0; } if (!this.isWebGL) { this.context.save(); this.context.scale(this._DPR, this._DPR); } else { this.context.viewport(0, 0, this.width * this._DPR, this.height * this._DPR); } this.onRender(this.context, this.width, this.height, dt); if (!this.isWebGL) this.context.restore(); this._then = now; }; this._renderHandler = function() { if (!this.running) return; if (!this._once) { this._lastFrame = requestAnimationFrame(this._renderHandler); } this.renderOnce(); }.bind(this); if (typeof options.onReady === "function") { options.onReady.call(this, context, this.width, this.height); } }