1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * Demo: 9 * <pre> 10 * var stage = new Hilo.Stage({ 11 * renderType:'canvas', 12 * container: containerElement, 13 * width: 320, 14 * height: 480 15 * }); 16 * </pre> 17 * @class Stage is the root of all visual object tree, any visual object will be render only after being added to Stage or any children elements of Stage. Normally, every hilo application start with an stage instance. 18 * @augments Container 19 * @param {Object} properties Properties parameters for the object. Includes all writable properties of this class. Some important like: 20 * <ul> 21 * <li><b>container</b>:String|HTMLElement - Assign the parent container element of the Stage in the page. It should be a dom container or an id. If this parameter is not given and canvas isn't in the dom tree, you should add the stage vanvas into the dom tree yourself, otherwise Stage will not render. optional.</li> 22 * <li><b>renderType</b>:String - Renering way: canvas|dom|webgl,default value is canvas, optional.</li> 23 * <li><b>canvas</b>:String|HTMLCanvasElement|HTMLElement - 指定舞台所对应的画布元素。它是一个canvas或普通的div,也可以传入元素的id。若为canvas,则使用canvas来渲染所有对象,否则使用dom+css来渲染。可选。</li> 24 * <li><b>width</b>:Number</li> - The width of the Stage, default value is the width of canvas, optional. 25 * <li><b>height</b>:Number</li> - The height of the Stage, default value is the height of canvas, optional. 26 * <li><b>paused</b>:Boolean</li> - Whether stop rendering the Stage, default value is false, optional. 27 * </ul> 28 * @module hilo/view/Stage 29 * @requires hilo/core/Hilo 30 * @requires hilo/core/Class 31 * @requires hilo/view/Container 32 * @requires hilo/renderer/CanvasRenderer 33 * @requires hilo/renderer/DOMRenderer 34 * @requires hilo/renderer/WebGLRenderer 35 * @requires hilo/util/browser 36 * @requires hilo/util/util 37 * @property {HTMLCanvasElement|HTMLElement} canvas The canvas the Stage is related to. It can be a canvas or a div element, readonly! 38 * @property {Renderer} renderer Stage renderer, readonly! 39 * @property {Boolean} paused Paused Stage rendering. 40 * @property {Object} viewport Rendering area of the Stage. Including properties like: left, top, width, height. readonly! 41 */ 42 var Stage = Class.create(/** @lends Stage.prototype */{ 43 Extends: Container, 44 constructor: function(properties){ 45 properties = properties || {}; 46 this.id = this.id || properties.id || Hilo.getUid('Stage'); 47 Stage.superclass.constructor.call(this, properties); 48 49 this._initRenderer(properties); 50 51 //init size 52 var width = this.width, height = this.height, 53 viewport = this.updateViewport(); 54 if(!properties.width) width = (viewport && viewport.width) || 320; 55 if(!properties.height) height = (viewport && viewport.height) || 480; 56 this.resize(width, height, true); 57 }, 58 59 canvas: null, 60 renderer: null, 61 paused: false, 62 viewport: null, 63 64 /** 65 * @private 66 */ 67 _initRenderer: function(properties){ 68 var canvas = properties.canvas; 69 var container = properties.container; 70 var renderType = properties.renderType||'canvas'; 71 72 if(typeof canvas === 'string') canvas = Hilo.getElement(canvas); 73 if(typeof container === 'string') container = Hilo.getElement(container); 74 75 if(!canvas){ 76 var canvasTagName = renderType === 'dom'?'div':'canvas'; 77 canvas = Hilo.createElement(canvasTagName, { 78 style: { 79 position: 'absolute' 80 } 81 }); 82 } 83 else if(!canvas.getContext){ 84 renderType = 'dom'; 85 } 86 87 this.canvas = canvas; 88 if(container) container.appendChild(canvas); 89 90 var props = {canvas:canvas, stage:this}; 91 switch(renderType){ 92 case 'dom': 93 this.renderer = new DOMRenderer(props); 94 break; 95 case 'webgl': 96 if(WebGLRenderer.isSupport()){ 97 this.renderer = new WebGLRenderer(props); 98 } 99 else{ 100 this.renderer = new CanvasRenderer(props); 101 } 102 break; 103 case 'canvas': 104 /* falls through */ 105 default: 106 this.renderer = new CanvasRenderer(props); 107 break; 108 } 109 }, 110 111 /** 112 * Add Stage canvas to DOM container. Note: this function overwrite View.addTo function. 113 * @param {HTMLElement} domElement An dom element. 114 * @returns {Stage} The Stage Object, chained call supported. 115 */ 116 addTo: function(domElement){ 117 var canvas = this.canvas; 118 if(canvas.parentNode !== domElement){ 119 domElement.appendChild(canvas); 120 } 121 return this; 122 }, 123 124 /** 125 * Invoke tick function and Stage will update and render. Developer may not need to use this funciton. 126 * @param {Number} delta The time had pass between this tick invoke and last tick invoke. 127 */ 128 tick: function(delta){ 129 if(!this.paused){ 130 this._render(this.renderer, delta); 131 } 132 }, 133 134 /** 135 * Turn on/off Stage response to DOM event. To make visual objects on the Stage interactive, use this function to turn on Stage's responses to events. 136 * @param {String|Array} type The event name or array that need to turn on/off. 137 * @param {Boolean} enabled Whether turn on or off the response method of stage DOM event. If not provided, default value is true. 138 * @returns {Stage} The Stage Object, chained call supported. 139 */ 140 enableDOMEvent: function(types, enabled){ 141 var me = this, 142 canvas = me.canvas, 143 handler = me._domListener || (me._domListener = function(e){me._onDOMEvent(e);}); 144 145 types = typeof types === 'string' ? [types] : types; 146 enabled = enabled !== false; 147 148 for(var i = 0; i < types.length; i++){ 149 var type = types[i]; 150 151 if(enabled){ 152 canvas.addEventListener(type, handler, false); 153 }else{ 154 canvas.removeEventListener(type, handler); 155 } 156 } 157 158 return me; 159 }, 160 161 /** 162 * DOM events handler function. This funciton will invoke events onto the visual object, which is on the position of the coordinate where the events is invoked. 163 * @private 164 */ 165 _onDOMEvent: function(e){ 166 var type = e.type, event = e, isTouch = type.indexOf('touch') == 0; 167 168 //calculate stageX/stageY 169 var posObj = e; 170 if(isTouch){ 171 var touches = e.touches, changedTouches = e.changedTouches; 172 posObj = (touches && touches.length) ? touches[0] : 173 (changedTouches && changedTouches.length) ? changedTouches[0] : null; 174 } 175 176 var x = posObj.pageX || posObj.clientX, y = posObj.pageY || posObj.clientY, 177 viewport = this.viewport || this.updateViewport(); 178 179 event.stageX = x = (x - viewport.left) / this.scaleX; 180 event.stageY = y = (y - viewport.top) / this.scaleY; 181 182 //鼠标事件需要阻止冒泡方法 Prevent bubbling on mouse events. 183 event.stopPropagation = function(){ 184 this._stopPropagationed = true; 185 }; 186 187 var obj = this.getViewAtPoint(x, y, true, false, true)||this, 188 canvas = this.canvas, target = this._eventTarget; 189 190 //fire mouseout/touchout event for last event target 191 var leave = type === 'mouseout'; 192 //当obj和target不同 且obj不是target的子元素时才触发out事件 fire out event when obj and target isn't the same as well as obj is not a child element to target. 193 if(target && (target != obj && (!target.contains || !target.contains(obj))|| leave)){ 194 var out = (type === 'touchmove') ? 'touchout' : 195 (type === 'mousemove' || leave || !obj) ? 'mouseout' : null; 196 if(out) { 197 var outEvent = util.copy({}, event); 198 outEvent.type = out; 199 outEvent.eventTarget = target; 200 target._fireMouseEvent(outEvent); 201 } 202 event.lastEventTarget = target; 203 this._eventTarget = null; 204 } 205 206 //fire event for current view 207 if(obj && obj.pointerEnabled && type !== 'mouseout'){ 208 event.eventTarget = this._eventTarget = obj; 209 obj._fireMouseEvent(event); 210 } 211 212 //set cursor for current view 213 if(!isTouch){ 214 var cursor = (obj && obj.pointerEnabled && obj.useHandCursor) ? 'pointer' : ''; 215 canvas.style.cursor = cursor; 216 } 217 218 //fix android: `touchmove` fires only once 219 if(browser.android && type === 'touchmove'){ 220 e.preventDefault(); 221 } 222 }, 223 224 /** 225 * Update the viewport (rendering area) which Stage show on the page. Invoke this function to update viewport when Stage canvas changes border, margin or padding properties. 226 * @returns {Object} The visible area of the Stage (the viewport property). 227 */ 228 updateViewport: function(){ 229 var canvas = this.canvas, viewport = null; 230 if(canvas.parentNode){ 231 viewport = this.viewport = Hilo.getElementRect(canvas); 232 } 233 return viewport; 234 }, 235 236 /** 237 * Resize the Stage. 238 * @param {Number} width The width of the new Stage. 239 * @param {Number} height The height of the new Stage. 240 * @param {Boolean} forceResize Whether forced to resize the Stage, means no matter the size of the Stage, force to change the size to keep Stage, canvas and window act at the same time. 241 */ 242 resize: function(width, height, forceResize){ 243 if(forceResize || this.width !== width || this.height !== height){ 244 this.width = width; 245 this.height = height; 246 this.renderer.resize(width, height); 247 this.updateViewport(); 248 } 249 } 250 251 }); 252