1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @namespace Hilo的基础核心方法集合。
  9  * @static
 10  * @module hilo/core/Hilo
 11  */
 12 var Hilo = (function(){
 13 
 14 var win = window, doc = document, docElem = doc.documentElement,
 15     uid = 0;
 16 
 17 return {
 18     /**
 19      * 获取一个全局唯一的id。如Stage1,Bitmap2等。
 20      * @param {String} prefix 生成id的前缀。
 21      * @returns {String} 全局唯一id。
 22      */
 23     getUid: function(prefix){
 24         var id = ++uid;
 25         if(prefix){
 26             var charCode = prefix.charCodeAt(prefix.length - 1);
 27             if (charCode >= 48 && charCode <= 57) prefix += "_"; //0至9之间添加下划线
 28             return prefix + id;
 29         }
 30         return id;
 31     },
 32 
 33     /**
 34      * 为指定的可视对象生成一个包含路径的字符串表示形式。如Stage1.Container2.Bitmap3。
 35      * @param {View} view 指定的可视对象。
 36      * @returns {String} 可视对象的字符串表示形式。
 37      */
 38     viewToString: function(view){
 39         var result, obj = view;
 40         while(obj){
 41             result = result ? (obj.id + '.' + result) : obj.id;
 42             obj = obj.parent;
 43         }
 44         return result;
 45     },
 46 
 47     /**
 48      * 简单的浅复制对象。
 49      * @param {Object} target 要复制的目标对象。
 50      * @param {Object} source 要复制的源对象。
 51      * @param {Boolean} strict 指示是否复制未定义的属性,默认为false,即不复制未定义的属性。
 52      * @returns {Object} 复制后的对象。
 53      */
 54     copy: function(target, source, strict){
 55         for(var key in source){
 56             if(!strict || target.hasOwnProperty(key) || target[key] !== undefined){
 57                 target[key] = source[key];
 58             }
 59         }
 60         return target;
 61     },
 62 
 63     /**
 64      * 浏览器特性集合。包括:
 65      * <ul>
 66      * <li><b>jsVendor</b> - 浏览器厂商CSS前缀的js值。比如:webkit。</li>
 67      * <li><b>cssVendor</b> - 浏览器厂商CSS前缀的css值。比如:-webkit-。</li>
 68      * <li><b>supportTransform</b> - 是否支持CSS Transform变换。</li>
 69      * <li><b>supportTransform3D</b> - 是否支持CSS Transform 3D变换。</li>
 70      * <li><b>supportStorage</b> - 是否支持本地存储localStorage。</li>
 71      * <li><b>supportTouch</b> - 是否支持触碰事件。</li>
 72      * <li><b>supportCanvas</b> - 是否支持canvas元素。</li>
 73      * </ul>
 74      */
 75     browser: (function(){
 76         var ua = navigator.userAgent;
 77         var data = {
 78             iphone: /iphone/i.test(ua),
 79             ipad: /ipad/i.test(ua),
 80             ipod: /ipod/i.test(ua),
 81             ios: /iphone|ipad|ipod/i.test(ua),
 82             android: /android/i.test(ua),
 83             webkit: /webkit/i.test(ua),
 84             chrome: /chrome/i.test(ua),
 85             safari: /safari/i.test(ua),
 86             firefox: /firefox/i.test(ua),
 87             ie: /msie/i.test(ua),
 88             opera: /opera/i.test(ua),
 89             supportTouch: 'ontouchstart' in win,
 90             supportCanvas: doc.createElement('canvas').getContext != null,
 91             supportStorage: false,
 92             supportOrientation: 'orientation' in win,
 93             supportDeviceMotion: 'ondevicemotion' in win
 94         };
 95 
 96         //`localStorage` is null or `localStorage.setItem` throws error in some cases (e.g. localStorage is disabled)
 97         try{
 98             var value = 'hilo';
 99             localStorage.setItem(value, value);
100             localStorage.removeItem(value);
101             data.supportStorage = true;
102         }catch(e){ };
103 
104         //vendro prefix
105         var jsVendor = data.jsVendor = data.webkit ? 'webkit' : data.firefox ? 'Moz' : data.opera ? 'O' : data.ie ? 'ms' : '';
106         var cssVendor = data.cssVendor = '-' + jsVendor + '-';
107 
108         //css transform/3d feature dectection
109         var testElem = doc.createElement('div'), style = testElem.style;
110         var supportTransform = style[jsVendor + 'Transform'] != undefined;
111         var supportTransform3D = style[jsVendor + 'Perspective'] != undefined;
112         if(supportTransform3D){
113             testElem.id = 'test3d';
114             style = doc.createElement('style');
115             style.textContent = '@media ('+ cssVendor +'transform-3d){#test3d{height:3px}}';
116             doc.head.appendChild(style);
117 
118             docElem.appendChild(testElem);
119             supportTransform3D = testElem.offsetHeight == 3;
120             doc.head.removeChild(style);
121             docElem.removeChild(testElem);
122         };
123         data.supportTransform = supportTransform;
124         data.supportTransform3D = supportTransform3D;
125 
126         return data;
127     })(),
128 
129     /**
130      * 事件类型枚举对象。包括:
131      * <ul>
132      * <li><b>POINTER_START</b> - 鼠标或触碰开始事件。对应touchstart或mousedown。</li>
133      * <li><b>POINTER_MOVE</b> - 鼠标或触碰移动事件。对应touchmove或mousemove。</li>
134      * <li><b>POINTER_END</b> - 鼠标或触碰结束事件。对应touchend或mouseup。</li>
135      * </ul>
136      */
137     event: (function(){
138         var supportTouch = 'ontouchstart' in win;
139         return {
140             POINTER_START: supportTouch ? 'touchstart' : 'mousedown',
141             POINTER_MOVE: supportTouch ? 'touchmove' : 'mousemove',
142             POINTER_END: supportTouch ? 'touchend' : 'mouseup'
143         };
144     })(),
145 
146     /**
147      * 可视对象对齐方式枚举对象。包括:
148      * <ul>
149      * <li><b>TOP_LEFT</b> - 左上角对齐。</li>
150      * <li><b>TOP</b> - 顶部居中对齐。</li>
151      * <li><b>TOP_RIGHT</b> - 右上角对齐。</li>
152      * <li><b>LEFT</b> - 左边居中对齐。</li>
153      * <li><b>CENTER</b> - 居中对齐。</li>
154      * <li><b>RIGHT</b> - 右边居中对齐。</li>
155      * <li><b>BOTTOM_LEFT</b> - 左下角对齐。</li>
156      * <li><b>BOTTOM</b> - 底部居中对齐。</li>
157      * <li><b>BOTTOM_RIGHT</b> - 右下角对齐。</li>
158      * </ul>
159      */
160     align: {
161         TOP_LEFT: 'TL', //top & left
162         TOP: 'T', //top & center
163         TOP_RIGHT: 'TR', //top & right
164         LEFT: 'L', //left & center
165         CENTER: 'C', //center
166         RIGHT: 'R', //right & center
167         BOTTOM_LEFT: 'BL', //bottom & left
168         BOTTOM: 'B', //bottom & center
169         BOTTOM_RIGHT: 'BR' //bottom & right
170     },
171 
172     /**
173      * 获取DOM元素在页面中的内容显示区域。
174      * @param {HTMLElement} elem DOM元素。
175      * @returns {Object} DOM元素的可视区域。格式为:{left:0, top:0, width:100, height:100}。
176      */
177     getElementRect: function(elem){
178         try{
179             //this fails if it's a disconnected DOM node
180             var bounds = elem.getBoundingClientRect();
181         }catch(e){
182             bounds = {top:elem.offsetTop, left:elem.offsetLeft, width:elem.offsetWidth, height:elem.offsetHeight};
183         }
184 
185         var offsetX = ((win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)) || 0;
186         var offsetY = ((win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0)) || 0;
187         var styles = win.getComputedStyle ? getComputedStyle(elem) : elem.currentStyle;
188         var parseIntFn = parseInt;
189 
190         var padLeft = (parseIntFn(styles.paddingLeft) + parseIntFn(styles.borderLeftWidth)) || 0;
191         var padTop = (parseIntFn(styles.paddingTop) + parseIntFn(styles.borderTopWidth)) || 0;
192         var padRight = (parseIntFn(styles.paddingRight) + parseIntFn(styles.borderRightWidth)) || 0;
193         var padBottom = (parseIntFn(styles.paddingBottom) + parseIntFn(styles.borderBottomWidth)) || 0;
194         var top = bounds.top || 0;
195         var left = bounds.left || 0;
196 
197         return {
198             left: left + offsetX + padLeft,
199             top: top + offsetY + padTop,
200             width: bounds.right - padRight - left - padLeft,
201             height: bounds.bottom - padBottom - top - padTop
202         };
203     },
204 
205     /**
206      * 创建一个DOM元素。可指定属性和样式。
207      * @param {String} type 要创建的DOM元素的类型。比如:'div'。
208      * @param {Object} properties 指定DOM元素的属性和样式。
209      * @returns {HTMLElement} 一个DOM元素。
210      */
211     createElement: function(type, properties){
212         var elem = doc.createElement(type), p, val, s;
213         for(p in properties){
214             val = properties[p];
215             if(p === 'style'){
216                 for(s in val) elem.style[s] = val[s];
217             }else{
218                 elem[p] = val;
219             }
220         }
221         return elem;
222     },
223 
224     /**
225      * 根据参数id获取一个DOM元素。此方法等价于document.getElementById(id)。
226      * @param {String} id 要获取的DOM元素的id。
227      * @returns {HTMLElement} 一个DOM元素。
228      */
229     getElement: function(id){
230         return doc.getElementById(id);
231     },
232 
233     /**
234      * 设置可视对象DOM元素的CSS样式。
235      * @param {View} obj 指定要设置CSS样式的可视对象。
236      * @private
237      */
238     setElementStyleByView: function(obj){
239         var drawable = obj.drawable,
240             style = drawable.domElement.style,
241             stateCache = obj._stateCache || (obj._stateCache = {}),
242             prefix = Hilo.browser.jsVendor, px = 'px', flag = false;
243 
244         if(this.cacheStateIfChanged(obj, ['visible'], stateCache)){
245             style.display = !obj.visible ? 'none' : '';
246         }
247         if(this.cacheStateIfChanged(obj, ['alpha'], stateCache)){
248             style.opacity = obj.alpha;
249         }
250         if(!obj.visible || obj.alpha <= 0) return;
251 
252         if(this.cacheStateIfChanged(obj, ['width'], stateCache)){
253             style.width = obj.width + px;
254         }
255         if(this.cacheStateIfChanged(obj, ['height'], stateCache)){
256             style.height = obj.height + px;
257         }
258         if(this.cacheStateIfChanged(obj, ['depth'], stateCache)){
259             style.zIndex = obj.depth + 1;
260         }
261         if(flag = this.cacheStateIfChanged(obj, ['pivotX', 'pivotY'], stateCache)){
262             style[prefix + 'TransformOrigin'] = obj.pivotX + px + ' ' + obj.pivotY + px;
263         }
264         if(this.cacheStateIfChanged(obj, ['x', 'y', 'rotation', 'scaleX', 'scaleY'], stateCache) || flag){
265             style[prefix + 'Transform'] = this.getTransformCSS(obj);
266         }
267         if(this.cacheStateIfChanged(obj, ['background'], stateCache)){
268             style.backgroundColor = obj.background;
269         }
270         if(!style.pointerEvents){
271             style.pointerEvents = 'none';
272         }
273 
274         //render image as background
275         var image = drawable.image;
276         if(image){
277             var src = image.src;
278             if(src !== stateCache.image){
279                 stateCache.image = src;
280                 style.backgroundImage = 'url(' + src + ')';
281             }
282 
283             var rect = drawable.rect;
284             if(rect){
285                 var sx = rect[0], sy = rect[1];
286                 if(sx !== stateCache.sx){
287                     stateCache.sx = sx;
288                     style.backgroundPositionX = -sx + px;
289                 }
290                 if(sy !== stateCache.sy){
291                     stateCache.sy = sy;
292                     style.backgroundPositionY = -sy + px;
293                 }
294             }
295         }
296 
297         //render mask
298         var mask = obj.mask;
299         if(mask){
300             var maskImage = mask.drawable.domElement.style.backgroundImage;
301             if(maskImage !== stateCache.maskImage){
302                 stateCache.maskImage = maskImage;
303                 style[prefix + 'MaskImage'] = maskImage;
304                 style[prefix + 'MaskRepeat'] = 'no-repeat';
305             }
306 
307             var maskX = mask.x, maskY = mask.y;
308             if(maskX !== stateCache.maskX || maskY !== stateCache.maskY){
309                 stateCache.maskX = maskX;
310                 stateCache.maskY = maskY;
311                 style[prefix + 'MaskPosition'] = maskX + px + ' ' + maskY + px;
312             }
313         }
314     },
315 
316     /**
317      * @private
318      */
319     cacheStateIfChanged: function(obj, propNames, stateCache){
320         var i, len, name, value, changed = false;
321         for(i = 0, len = propNames.length; i < len; i++){
322             name = propNames[i];
323             value = obj[name];
324             if(value != stateCache[name]){
325                 stateCache[name] = value;
326                 changed = true;
327             }
328         }
329         return changed;
330     },
331 
332     /**
333      * 生成可视对象的CSS变换样式。
334      * @param {View} obj 指定生成CSS变换样式的可视对象。
335      * @returns {String} 生成的CSS样式字符串。
336      */
337     getTransformCSS: function(obj){
338         var use3d = this.browser.supportTransform3D,
339             str3d = use3d ? '3d' : '';
340 
341         return 'translate' + str3d + '(' + (obj.x - obj.pivotX) + 'px, ' + (obj.y - obj.pivotY) + (use3d ? 'px, 0px)' : 'px)')
342              + 'rotate' + str3d + (use3d ? '(0, 0, 1, ' : '(') + obj.rotation + 'deg)'
343              + 'scale' + str3d + '(' + obj.scaleX + ', ' + obj.scaleY + (use3d ? ', 1)' : ')');
344     }
345 };
346 
347 })();