1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 
  8 /**
  9  * @language=en
 10  * @namespace Hilo The underlying core set of methods.
 11  * @static
 12  * @module hilo/core/Hilo
 13  */
 14 var Hilo = (function(){
 15 
 16 var win = window, doc = document, docElem = doc.documentElement,
 17     uid = 0;
 18 
 19 return {
 20     /**
 21      * @language=en
 22      * Gets a globally unique id. Such as Stage1, Bitmap2 etc.
 23      * @param {String} prefix Generated id's prefix.
 24      * @returns {String} Globally unique id.
 25      */
 26     getUid: function(prefix){
 27         var id = ++uid;
 28         if(prefix){
 29             var charCode = prefix.charCodeAt(prefix.length - 1);
 30             if (charCode >= 48 && charCode <= 57) prefix += "_"; //0至9之间添加下划线
 31             return prefix + id;
 32         }
 33         return id;
 34     },
 35 
 36     /**
 37      * @language=en
 38      * Generates a string representation that contains a path to the specified visual object. Such as Stage1.Container2.Bitmap3.
 39      * @param {View} view Specified visual object.
 40      * @returns {String} String representation of the visual object.
 41      */
 42     viewToString: function(view){
 43         var result, obj = view;
 44         while(obj){
 45             result = result ? (obj.id + '.' + result) : obj.id;
 46             obj = obj.parent;
 47         }
 48         return result;
 49     },
 50 
 51     /**
 52      * @language=en
 53      * Simple shallow copy objects.
 54      * @param {Object} target Target object to copy to.
 55      * @param {Object} source Source object to copy.
 56      * @param {Boolean} strict Indicates whether replication is undefined property, default is false, i.e., undefined attributes are not copied.
 57      * @returns {Object} Object after copying.
 58      */
 59     copy: function(target, source, strict){
 60         for(var key in source){
 61             if(!strict || target.hasOwnProperty(key) || target[key] !== undefined){
 62                 target[key] = source[key];
 63             }
 64         }
 65         return target;
 66     },
 67 
 68     /**
 69      * @language=en
 70      * Browser feature set includes:
 71      * <ul>
 72      * <li><b>jsVendor</b> - Browser vendors js value CSS prefix. For example: webkit.</li>
 73      * <li><b>cssVendor</b> - Browser vendors css value CSS prefix.</li>
 74      * <li><b>supportTransform</b> - Whether to support CSS Transform transformation.</li>
 75      * <li><b>supportTransform3D</b> - Whether to support CSS Transform 3D transformation.</li>
 76      * <li><b>supportStorage</b> - Whether to support local stores like localStorage.</li>
 77      * <li><b>supportTouch</b> - Whether to support the touch event.</li>
 78      * <li><b>supportCanvas</b> - Whether to support the canvas element.</li>
 79      * </ul>
 80      */
 81     browser: (function(){
 82         var ua = navigator.userAgent;
 83         var data = {
 84             iphone: /iphone/i.test(ua),
 85             ipad: /ipad/i.test(ua),
 86             ipod: /ipod/i.test(ua),
 87             ios: /iphone|ipad|ipod/i.test(ua),
 88             android: /android/i.test(ua),
 89             webkit: /webkit/i.test(ua),
 90             chrome: /chrome/i.test(ua),
 91             safari: /safari/i.test(ua),
 92             firefox: /firefox/i.test(ua),
 93             ie: /msie/i.test(ua),
 94             opera: /opera/i.test(ua),
 95             supportTouch: 'ontouchstart' in win,
 96             supportCanvas: doc.createElement('canvas').getContext != null,
 97             supportStorage: false,
 98             supportOrientation: 'orientation' in win,
 99             supportDeviceMotion: 'ondevicemotion' in win
100         };
101 
102         //`localStorage` is null or `localStorage.setItem` throws error in some cases (e.g. localStorage is disabled)
103         try{
104             var value = 'hilo';
105             localStorage.setItem(value, value);
106             localStorage.removeItem(value);
107             data.supportStorage = true;
108         }catch(e){ };
109 
110         //vendro prefix
111         var jsVendor = data.jsVendor = data.webkit ? 'webkit' : data.firefox ? 'Moz' : data.opera ? 'O' : data.ie ? 'ms' : '';
112         var cssVendor = data.cssVendor = '-' + jsVendor + '-';
113 
114         //css transform/3d feature dectection
115         var testElem = doc.createElement('div'), style = testElem.style;
116         var supportTransform = style[jsVendor + 'Transform'] != undefined;
117         var supportTransform3D = style[jsVendor + 'Perspective'] != undefined;
118         if(supportTransform3D){
119             testElem.id = 'test3d';
120             style = doc.createElement('style');
121             style.textContent = '@media ('+ cssVendor +'transform-3d){#test3d{height:3px}}';
122             doc.head.appendChild(style);
123 
124             docElem.appendChild(testElem);
125             supportTransform3D = testElem.offsetHeight == 3;
126             doc.head.removeChild(style);
127             docElem.removeChild(testElem);
128         };
129         data.supportTransform = supportTransform;
130         data.supportTransform3D = supportTransform3D;
131 
132         return data;
133     })(),
134 
135     /**
136      * @language=en
137      * Event enumeration objects include:
138      * <ul>
139      * <li><b>POINTER_START</b> - Mouse or touch start event. Corresponds to touchstart or mousedown.</li>
140      * <li><b>POINTER_MOVE</b> - Mouse or touch move event. Corresponds to touchmove or mousemove.</li>
141      * <li><b>POINTER_END</b> - Mouse or touch end event. Corresponds to touchend or mouseup.</li>
142      * </ul>
143      */
144     event: (function(){
145         var supportTouch = 'ontouchstart' in win;
146         return {
147             POINTER_START: supportTouch ? 'touchstart' : 'mousedown',
148             POINTER_MOVE: supportTouch ? 'touchmove' : 'mousemove',
149             POINTER_END: supportTouch ? 'touchend' : 'mouseup'
150         };
151     })(),
152 
153     /**
154      * @language=en
155      * Visual object alinment enumeration objects include:
156      * <ul>
157      * <li><b>TOP_LEFT</b> - Align the top left corner.</li>
158      * <li><b>TOP</b> - Top center alignment.</li>
159      * <li><b>TOP_RIGHT</b> - Align the top right corner.</li>
160      * <li><b>LEFT</b> - Left center alignment.</li>
161      * <li><b>CENTER</b> - Align center.</li>
162      * <li><b>RIGHT</b> - Right center alignment.</li>
163      * <li><b>BOTTOM_LEFT</b> - Align the bottom left corner.</li>
164      * <li><b>BOTTOM</b> - Bottom center alignment.</li>
165      * <li><b>BOTTOM_RIGHT</b> - Align the bottom right corner.</li>
166      * </ul>
167      */
168     align: {
169         TOP_LEFT: 'TL', //top & left
170         TOP: 'T', //top & center
171         TOP_RIGHT: 'TR', //top & right
172         LEFT: 'L', //left & center
173         CENTER: 'C', //center
174         RIGHT: 'R', //right & center
175         BOTTOM_LEFT: 'BL', //bottom & left
176         BOTTOM: 'B', //bottom & center
177         BOTTOM_RIGHT: 'BR' //bottom & right
178     },
179 
180     /**
181      * @language=en
182      * Get DOM element content in the page display area.
183      * @param {HTMLElement} elem DOM elements.
184      * @returns {Object} Viewable area DOM elements. Format is: {left:0, top:0, width:100, height:100}.
185      */
186     getElementRect: function(elem){
187         try{
188             //this fails if it's a disconnected DOM node
189             var bounds = elem.getBoundingClientRect();
190         }catch(e){
191             bounds = {top:elem.offsetTop, left:elem.offsetLeft, right:elem.offsetLeft + elem.offsetWidth, bottom:elem.offsetTop + elem.offsetHeight};
192         }
193 
194         var offsetX = ((win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)) || 0;
195         var offsetY = ((win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0)) || 0;
196         var styles = win.getComputedStyle ? getComputedStyle(elem) : elem.currentStyle;
197         var parseIntFn = parseInt;
198 
199         var padLeft = (parseIntFn(styles.paddingLeft) + parseIntFn(styles.borderLeftWidth)) || 0;
200         var padTop = (parseIntFn(styles.paddingTop) + parseIntFn(styles.borderTopWidth)) || 0;
201         var padRight = (parseIntFn(styles.paddingRight) + parseIntFn(styles.borderRightWidth)) || 0;
202         var padBottom = (parseIntFn(styles.paddingBottom) + parseIntFn(styles.borderBottomWidth)) || 0;
203 
204         var top = bounds.top || 0;
205         var left = bounds.left || 0;
206         var right = bounds.right || 0;
207         var bottom = bounds.bottom || 0;
208 
209         return {
210             left: left + offsetX + padLeft,
211             top: top + offsetY + padTop,
212             width: right - padRight - left - padLeft,
213             height: bottom - padBottom - top - padTop
214         };
215     },
216 
217     /**
218      * @language=en
219      * Create a DOM element. You can specify properties and styles.
220      * @param {String} type DOM element type to be created. Such as: 'div'.
221      * @param {Object} properties Properties and styles for DOM element.
222      * @returns {HTMLElement} A DOM element.
223      */
224     createElement: function(type, properties){
225         var elem = doc.createElement(type), p, val, s;
226         for(p in properties){
227             val = properties[p];
228             if(p === 'style'){
229                 for(s in val) elem.style[s] = val[s];
230             }else{
231                 elem[p] = val;
232             }
233         }
234         return elem;
235     },
236 
237     /**
238      * @language=en
239      * Gets a DOM element according to the parameter id. This method is equivalent to document.getElementById(id).
240      * @param {String} id id of the DOM element you want to get.
241      * @returns {HTMLElement} A DOM element.
242      */
243     getElement: function(id){
244         return doc.getElementById(id);
245     },
246 
247     /**
248      * @language=en
249      * Set visual object DOM element CSS style.
250      * @param {View} obj Specifies the CSS style to set the visual object.
251      * @private
252      */
253     setElementStyleByView: function(obj){
254         var drawable = obj.drawable,
255             style = drawable.domElement.style,
256             stateCache = obj._stateCache || (obj._stateCache = {}),
257             prefix = Hilo.browser.jsVendor, px = 'px', flag = false;
258 
259         if(this.cacheStateIfChanged(obj, ['visible'], stateCache)){
260             style.display = !obj.visible ? 'none' : '';
261         }
262         if(this.cacheStateIfChanged(obj, ['alpha'], stateCache)){
263             style.opacity = obj.alpha;
264         }
265         if(!obj.visible || obj.alpha <= 0) return;
266 
267         if(this.cacheStateIfChanged(obj, ['width'], stateCache)){
268             style.width = obj.width + px;
269         }
270         if(this.cacheStateIfChanged(obj, ['height'], stateCache)){
271             style.height = obj.height + px;
272         }
273         if(this.cacheStateIfChanged(obj, ['depth'], stateCache)){
274             style.zIndex = obj.depth + 1;
275         }
276         if(flag = this.cacheStateIfChanged(obj, ['pivotX', 'pivotY'], stateCache)){
277             style[prefix + 'TransformOrigin'] = obj.pivotX + px + ' ' + obj.pivotY + px;
278         }
279         if(this.cacheStateIfChanged(obj, ['x', 'y', 'rotation', 'scaleX', 'scaleY'], stateCache) || flag){
280             style[prefix + 'Transform'] = this.getTransformCSS(obj);
281         }
282         if(this.cacheStateIfChanged(obj, ['background'], stateCache)){
283             style.backgroundColor = obj.background;
284         }
285         if(!style.pointerEvents){
286             style.pointerEvents = 'none';
287         }
288 
289         //render image as background
290         var image = drawable.image;
291         if(image){
292             var src = image.src;
293             if(src !== stateCache.image){
294                 stateCache.image = src;
295                 style.backgroundImage = 'url(' + src + ')';
296             }
297 
298             var rect = drawable.rect;
299             if(rect){
300                 var sx = rect[0], sy = rect[1];
301                 if(sx !== stateCache.sx){
302                     stateCache.sx = sx;
303                     style.backgroundPositionX = -sx + px;
304                 }
305                 if(sy !== stateCache.sy){
306                     stateCache.sy = sy;
307                     style.backgroundPositionY = -sy + px;
308                 }
309             }
310         }
311 
312         //render mask
313         var mask = obj.mask;
314         if(mask){
315             var maskImage = mask.drawable.domElement.style.backgroundImage;
316             if(maskImage !== stateCache.maskImage){
317                 stateCache.maskImage = maskImage;
318                 style[prefix + 'MaskImage'] = maskImage;
319                 style[prefix + 'MaskRepeat'] = 'no-repeat';
320             }
321 
322             var maskX = mask.x, maskY = mask.y;
323             if(maskX !== stateCache.maskX || maskY !== stateCache.maskY){
324                 stateCache.maskX = maskX;
325                 stateCache.maskY = maskY;
326                 style[prefix + 'MaskPosition'] = maskX + px + ' ' + maskY + px;
327             }
328         }
329     },
330 
331     /**
332      * @private
333      */
334     cacheStateIfChanged: function(obj, propNames, stateCache){
335         var i, len, name, value, changed = false;
336         for(i = 0, len = propNames.length; i < len; i++){
337             name = propNames[i];
338             value = obj[name];
339             if(value != stateCache[name]){
340                 stateCache[name] = value;
341                 changed = true;
342             }
343         }
344         return changed;
345     },
346 
347     /**
348      * @language=en
349      * Generated visual object CSS style transformation.
350      * @param {View} obj Specifies visual object whose CSS style must be got.
351      * @returns {String} String representation of the CSS style.
352      */
353     getTransformCSS: function(obj){
354         var use3d = this.browser.supportTransform3D,
355             str3d = use3d ? '3d' : '';
356 
357         return 'translate' + str3d + '(' + (obj.x - obj.pivotX) + 'px, ' + (obj.y - obj.pivotY) + (use3d ? 'px, 0px)' : 'px)')
358              + 'rotate' + str3d + (use3d ? '(0, 0, 1, ' : '(') + obj.rotation + 'deg)'
359              + 'scale' + str3d + '(' + obj.scaleX + ', ' + obj.scaleY + (use3d ? ', 1)' : ')');
360     }
361 };
362 
363 })();