webkit-box & translate 的组合--流畅的滑动体验
【转】http://www.cnblogs.com/hongru/archive/2011/10/10/2203744.html
webkit-box & translate 的组合--流畅的滑动体验
【注:本文所有的代码和实例仅在chrome和safari等webkit内核的浏览器测试通过】
如果说从web Pages 能够转到web app时代,那么css3和html5其他相关技术一定是巨大的功臣。
唯一的遗憾就是pc端浏览器的泛滥导致了我们不得不走所谓的优雅降级,而且这种降级是降到新技术几乎木有多大的用武之地。
于是,客户端还算统一的移动端开始成了一个大的试验田。能够让众人大肆的在上面舒展拳脚。诸如众多新起的ui库或者框架(jquery-mobile, sencha, phoneGap ...),可见在移动终端上确实还有不小的田地。纵使如此,效率仍旧成为一个最大的瓶颈。
之前有一种尝试是用CSS3的transfrom或者animation给一个duration和ease的属性来做动画,这样不管改变任何style样式,都会根据这个ease有缓动的效果。
例如:
/* webkit */
-webkit-transition-duration: 500ms;
在webkit内核浏览器下,只要有这个属性,再去改变这个元素任何的样式,它都会以一个默认的缓动效果完成。
| 001 | /** | 
| 002 |  * CSS3 animation by transform | 
| 003 |  * @example | 
| 004 |  * Let(el) | 
| 005 |  *      .to(500, 200) | 
| 006 |  *      .rotate(180) | 
| 007 |  *      .scale(.5) | 
| 008 |  *      .set({ | 
| 009 |  *          background-color: 'red', | 
| 010 |  *          border-color: 'green' | 
| 011 |  *      }) | 
| 012 |  *      .duration(2000) | 
| 013 |  *      .skew(50, -10) | 
| 014 |  *      .then() | 
| 015 |  *          .set('opacity', .5) | 
| 016 |  *          .duration('1s') | 
| 017 |  *          .scale(1) | 
| 018 |  *          .pop() | 
| 019 |  *      .end(); | 
| 020 |  */ | 
| 021 | 
| 022 | (function(win, undefined) { | 
| 023 |      | 
| 024 |     varinitializing = false, | 
| 025 |         superTest = /horizon/.test(function() {horizon;}) ? /\b_super\b/ : /.*/; | 
| 026 |     // 临时Class | 
| 027 |     this.Class = function() {}; | 
| 028 |     // 继承方法extend | 
| 029 |     Class.extend = function(prop) { | 
| 030 |         var_super = this.prototype; | 
| 031 |         //创建一个实例,但不执行init | 
| 032 |         initializing = true; | 
| 033 |         varprototype = newthis(); | 
| 034 |         initializing = false; | 
| 035 | 
| 036 |         for(varname inprop) { | 
| 037 |             // 用闭包保证多级继承不会污染 | 
| 038 |             prototype[name] = (typeofprop[name] === 'function'&& typeof_super[name] === 'function'&& superTest.test(prop[name])) ? (function(name, fn) { | 
| 039 |                     returnfunction() { | 
| 040 |                         vartemp = this._super;  | 
| 041 |                         // 当前子类通过_super继承父类 | 
| 042 |                         this._super = _super[name]; | 
| 043 |                         //继承方法执行完毕后还原 | 
| 044 |                         varret = fn.apply(this, arguments); | 
| 045 |                         this._super = temp; | 
| 046 | 
| 047 |                         returnret; | 
| 048 |                     } | 
| 049 |                 })(name, prop[name]) : prop[name]; | 
| 050 |         } | 
| 051 |          | 
| 052 |         //真实的constructor | 
| 053 |         functionClass () { | 
| 054 |             if(!initializing && this.init) { | 
| 055 |                 this.init.apply(this, arguments); | 
| 056 |             } | 
| 057 |         } | 
| 058 |         Class.prototype = prototype; | 
| 059 |         Class.constructor = Class; | 
| 060 |         Class.extend = arguments.callee; | 
| 061 | 
| 062 |         returnClass; | 
| 063 |     } | 
| 064 |      | 
| 065 | 
| 066 |     // 样式为数字+px 的属性 | 
| 067 |     varmap = { | 
| 068 |         'top': 'px', | 
| 069 |         'left': 'px', | 
| 070 |         'right': 'px', | 
| 071 |         'bottom': 'px', | 
| 072 |         'width': 'px', | 
| 073 |         'height': 'px', | 
| 074 |         'font-size': 'px', | 
| 075 |         'margin': 'px', | 
| 076 |         'margin-top': 'px', | 
| 077 |         'margin-left': 'px', | 
| 078 |         'margin-right': 'px', | 
| 079 |         'margin-bottom': 'px', | 
| 080 |         'padding': 'px', | 
| 081 |         'padding-left': 'px', | 
| 082 |         'padding-right': 'px', | 
| 083 |         'padding-top': 'px', | 
| 084 |         'padding-bottom': 'px', | 
| 085 |         'border-width': 'px' | 
| 086 |     }; | 
| 087 | 
| 088 |     /** | 
| 089 |      * Let package | 
| 090 |      */ | 
| 091 |     varLet = function(selector) { | 
| 092 |         varel = Let.G(selector); | 
| 093 |         returnnewAnim(el); | 
| 094 |     }; | 
| 095 |     Let.defaults = { | 
| 096 |         duration: 500 | 
| 097 |     }; | 
| 098 |     Let.ease = { | 
| 099 |         'in': 'ease-in', | 
| 100 |         'out': 'ease-out', | 
| 101 |         'in-out': 'ease-in-out', | 
| 102 |         'snap': 'cubic-bezier(0,1,.5,1)' | 
| 103 |     }; | 
| 104 |     Let.G = function(selector) { | 
| 105 |         if(typeofselector != 'string'&& selector.nodeType == 1) { | 
| 106 |             returnselector; | 
| 107 |         } | 
| 108 |         returndocument.getElementById(selector) || document.querySelectorAll(selector)[0];      | 
| 109 |     }; | 
| 110 | 
| 111 |     /** | 
| 112 |      * EventEmitter | 
| 113 |      * {Class} | 
| 114 |      */ | 
| 115 |     varEventEmitter = Class.extend({ | 
| 116 |         init: function() { | 
| 117 |             this.callbacks = {}; | 
| 118 |         },       | 
| 119 |         on: function(event, fn) { | 
| 120 |             (this.callbacks[event] = this.callbacks[event] || []).push(fn); | 
| 121 |             returnthis; | 
| 122 |         }, | 
| 123 |         /** | 
| 124 |          * param {event} 指定event | 
| 125 |          * params 指定event的callback的参数 | 
| 126 |          */ | 
| 127 |         fire: function(event) { | 
| 128 |             varargs = Array.prototype.slice.call(arguments, 1), | 
| 129 |                 callbacks = this.callbacks[event], | 
| 130 |                 len; | 
| 131 |             if(callbacks) { | 
| 132 |                 for(vari = 0, len = callbacks.length; i < len; i ++) { | 
| 133 |                     callbacks[i].apply(this, args); | 
| 134 |                 } | 
| 135 |             } | 
| 136 |             returnthis; | 
| 137 |         } | 
| 138 |                  | 
| 139 |     }); | 
| 140 | 
| 141 |     /** | 
| 142 |      * Anim | 
| 143 |      * {Class} | 
| 144 |      * @inherit from EventEmitter | 
| 145 |      */ | 
| 146 |     varAnim = EventEmitter.extend({ | 
| 147 |         init: function(el) { | 
| 148 |             this._super(); | 
| 149 | 
| 150 |             if(!(thisinstanceofAnim)) { | 
| 151 |                 returnnewAnim(el); | 
| 152 |             } | 
| 153 | 
| 154 |             this.el = el; | 
| 155 |             this._props = {}; | 
| 156 |             this._rotate = 0; | 
| 157 |             this._transitionProps = []; | 
| 158 |             this._transforms = []; | 
| 159 |             this.duration(Let.defaults.duration); | 
| 160 |              | 
| 161 |         }, | 
| 162 |         transform : function(transform) { | 
| 163 |             this._transforms.push(transform); | 
| 164 |             returnthis; | 
| 165 |         }, | 
| 166 |         // skew methods | 
| 167 |         skew: function(x, y) { | 
| 168 |             y = y || 0; | 
| 169 |             returnthis.transform('skew('+ x +'deg, '+ y +'deg)'); | 
| 170 |         }, | 
| 171 |         skewX: function(x) { | 
| 172 |             returnthis.transform('skewX('+ x +'deg)');     | 
| 173 |         }, | 
| 174 |         skewY: function(y) { | 
| 175 |             returnthis.transform('skewY('+ y +'deg)');     | 
| 176 |         }, | 
| 177 |         // translate methods | 
| 178 |         translate: function(x, y) { | 
| 179 |             y = y || 0; | 
| 180 |             returnthis.transform('translate('+ x +'px, '+ y +'px)'); | 
| 181 |         }, | 
| 182 |         to: function(x, y) { | 
| 183 |             returnthis.translate(x, y);     | 
| 184 |         }, | 
| 185 |         translateX: function(x) { | 
| 186 |             returnthis.transform('translateX('+ x +'px)');          | 
| 187 |         }, | 
| 188 |         x: function(x) { | 
| 189 |             returnthis.translateX(x);    | 
| 190 |         }, | 
| 191 |         translateY: function(y) { | 
| 192 |             returnthis.transform('translateY('+ y +'px)');          | 
| 193 |         }, | 
| 194 |         y: function(y) { | 
| 195 |             returnthis.translateY(y);    | 
| 196 |         }, | 
| 197 |         // scale methods | 
| 198 |         scale: function(x, y) { | 
| 199 |             y = (y == null) ? x : y; | 
| 200 |             returnthis.transform('scale('+ x +', '+ y +')'); | 
| 201 |         }, | 
| 202 |         scaleX: function(x) { | 
| 203 |             returnthis.transform('scaleX('+ x +')'); | 
| 204 |         }, | 
| 205 |         scaleY: function(y) { | 
| 206 |             returnthis.transform('scaleY('+ y +')'); | 
| 207 |         }, | 
| 208 |         // rotate methods | 
| 209 |         rotate: function(n) { | 
| 210 |             returnthis.transform('rotate('+ n +'deg)'); | 
| 211 |         }, | 
| 212 | 
| 213 |         // set transition ease | 
| 214 |         ease: function(fn) { | 
| 215 |             fn = Let.ease[fn] || fn || 'ease'; | 
| 216 |             returnthis.setVendorProperty('transition-timing-function', fn); | 
| 217 |         }, | 
| 218 | 
| 219 |         //set duration time | 
| 220 |         duration: function(n) { | 
| 221 |             n = this._duration = (typeofn == 'string') ? parseFloat(n)*1000 : n; | 
| 222 |             returnthis.setVendorProperty('transition-duration', n + 'ms'); | 
| 223 |         }, | 
| 224 | 
| 225 |         // set delay time | 
| 226 |         delay: function(n) { | 
| 227 |             n = (typeofn == 'string') ? parseFloat(n) * 1000 : n; | 
| 228 |             returnthis.setVendorProperty('transition-delay', n + 'ms'); | 
| 229 |         }, | 
| 230 | 
| 231 |         // set property to val | 
| 232 |         setProperty: function(prop, val) { | 
| 233 |             this._props[prop] = val; | 
| 234 |             returnthis; | 
| 235 |         }, | 
| 236 |         setVendorProperty: function(prop, val) { | 
| 237 |             this.setProperty('-webkit-'+ prop, val); | 
| 238 |             this.setProperty('-moz-'+ prop, val); | 
| 239 |             this.setProperty('-ms-'+ prop, val); | 
| 240 |             this.setProperty('-o-'+ prop, val); | 
| 241 |             returnthis; | 
| 242 |         }, | 
| 243 |         set: function(prop, val) { | 
| 244 |             var_store = {}; | 
| 245 |             if(typeofprop == 'string'&& val != undefined) { | 
| 246 |                 _store[prop] = val; | 
| 247 |             } elseif(typeofprop == 'object'&& prop.constructor.prototype.hasOwnProperty('hasOwnProperty')) { | 
| 248 |                 _store = prop; | 
| 249 |             } | 
| 250 |              | 
| 251 |             for(varkey in_store) { | 
| 252 |                 this.transition(key); | 
| 253 |                 if(typeof_store[key] == 'number'&& map[key]) { | 
| 254 |                     _store[key] += map[key]; | 
| 255 |                 } | 
| 256 |                 this._props[key] = _store[key]; | 
| 257 |             } | 
| 258 |             returnthis; | 
| 259 | 
| 260 |         }, | 
| 261 |          | 
| 262 |         // add value to a property | 
| 263 |         add: function(prop, val) { | 
| 264 |             varself = this; | 
| 265 |             returnthis.on('start', function() { | 
| 266 |                 varcurr = parseInt(self.current(prop), 10); | 
| 267 |                 self.set(prop, curr + val + 'px'); | 
| 268 |             }) | 
| 269 |         }, | 
| 270 |         // sub value to a property | 
| 271 |         sub: function(prop, val) { | 
| 272 |             varself = this; | 
| 273 |             returnthis.on('start', function() { | 
| 274 |                 varcurr = parseInt(self.current(prop), 10); | 
| 275 |                 self.set(prop, curr - val + 'px'); | 
| 276 |             }) | 
| 277 |         }, | 
| 278 |         current: function(prop) { | 
| 279 |             return!!window.getComputedStyle ? document.defaultView.getComputedStyle(this.el, null).getPropertyValue(prop) : this.el.currentStyle(prop); | 
| 280 |         }, | 
| 281 | 
| 282 |         transition: function(prop) { | 
| 283 |             for(vari = 0; i < this._transitionProps.length; i ++) { | 
| 284 |                 if(this._transitionProps[i] == prop) { | 
| 285 |                     returnthis; | 
| 286 |                 } | 
| 287 |             } | 
| 288 | 
| 289 |             this._transitionProps.push(prop); | 
| 290 |             returnthis; | 
| 291 |         }, | 
| 292 |         applyPropertys: function() { | 
| 293 |             varprops = this._props, | 
| 294 |                 el = this.el; | 
| 295 |             for(varprop inprops) { | 
| 296 |                 if(props.hasOwnProperty(prop)) { | 
| 297 |                     el.style.setProperty ? el.style.setProperty(prop, props[prop], '') : el.style[prop] = props[prop]; | 
| 298 |                 } | 
| 299 |             } | 
| 300 |             returnthis; | 
| 301 |         }, | 
| 302 |          | 
| 303 |         // then | 
| 304 |         then: function(fn) { | 
| 305 |             if(fn instanceofAnim) { | 
| 306 |                 this.on('end', function() { | 
| 307 |                     fn.end();        | 
| 308 |                 }) | 
| 309 |             } elseif(typeoffn == 'function') { | 
| 310 |                 this.on('end', fn); | 
| 311 |             } else{ | 
| 312 |                 varclone = newAnim(this.el); | 
| 313 |                 clone._transforms = this._transforms.slice(0); | 
| 314 |                 this.then(clone); | 
| 315 |                 clone.parent = this; | 
| 316 |                 returnclone; | 
| 317 |             } | 
| 318 | 
| 319 |             returnthis; | 
| 320 |         }, | 
| 321 |         pop: function() { | 
| 322 |             returnthis.parent;   | 
| 323 |         }, | 
| 324 |         end: function(fn) { | 
| 325 |             varself = this; | 
| 326 |             this.fire('start'); | 
| 327 | 
| 328 |             if(this._transforms.length > 0) { | 
| 329 |                 this.setVendorProperty('transform', this._transforms.join(' ')); | 
| 330 |             } | 
| 331 | 
| 332 |             this.setVendorProperty('transition-properties', this._transitionProps.join(', ')); | 
| 333 |             this.applyPropertys(); | 
| 334 | 
| 335 |             if(fn) { this.then(fn) } | 
| 336 | 
| 337 |             setTimeout(function() { | 
| 338 |                 self.fire('end');        | 
| 339 |             }, this._duration); | 
| 340 | 
| 341 |             returnthis; | 
| 342 |         } | 
| 343 |          | 
| 344 |     }); | 
| 345 | 
| 346 |     this.Let = win.Let = Let; | 
| 347 |      | 
| 348 | 
| 349 |  })(window) | 
比如下面代码:
| 01 | <div id="test"></div> | 
| 02 | <script> | 
| 03 | Let('#test') | 
| 04 |     .to(200, 200) | 
| 05 |     .rotate(1000) | 
| 06 |     .scale(.5) | 
| 07 |     .set({ | 
| 08 |         'background-color': 'red', | 
| 09 |         'width': 300 | 
| 10 |     }) | 
| 11 |     .duration(2000) | 
| 12 |     .then() | 
| 13 |         .set('opacity', .5) | 
| 14 |         .set('height', 200) | 
| 15 |         .duration('1s') | 
| 16 |         .scale(1.5) | 
| 17 |         .to(300, 300) | 
| 18 |         .pop() | 
| 19 |     .end() | 
| 20 |      | 
| 21 | </script> | 
这样子有好处是可以针对所有的style样式。所以可以用同样的方式来对 left, top,margin-left,margin-top 之类的css2 的style属性来完成dom的相应变化。
但是,其实,用transform或者animation来操作css2的style属性。效率依然不高。在当前的移动终端,ipad还ok(毕竟是乔帮主的产品),iphone和android pad上执行效率在大部分情况下很难达到优秀app所要求的体验。
所以要做滑动之类的改变dom位置的体验。更好的实现应该是用纯粹的translate来改变位置,为了更好的与之配合,布局就尤为重要。
下面看看webkit提供的 display:-webkit-box; 亦即
Flexible Box Module
我称其为【流体盒模型】
W3C草案(http://www.w3.org/TR/css3-flexbox/)的描述 如下:
a CSS box model optimized for interface design. It provides an additional layout system alongside the ones already in CSS. [CSS21] In this new box model, the children of a box are laid out either horizontally or vertically, and unused space can be assigned to a particular child or distributed among the children by assignment of “flex” to the children that should expand. Nesting of these boxes (horizontal inside vertical, or vertical inside horizontal) can be used to build layouts in two dimensions. This model is based on the box model in the XUL user-interface language used for the user interface of many Mozilla-based applications (such as Firefox).
偶英文蹩脚,就不翻译了,用另外一番话来看它的意思:
1.之前要实现横列的web布局,通常就是float或者display:inline-block; 但是都不能做到真正的流体布局。至少width要自己去算百分比。
2.flexible box 就可以实现真正意义上的流体布局。只要给出相应属性,浏览器会帮我们做额外的计算。
提供的关于盒模型的几个属性:
| box-orient           子元素排列 vertical or horizontal | 
| box-flex             兄弟元素之间比例,仅作一个系数 | 
| box-align            box 排列 | 
| box-direction        box 方向 | 
| box-flex-group       以组为单位的流体系数 | 
| box-lines             | 
| box-ordinal-group    以组为单位的子元素排列方向 | 
| box-pack | 
以下是关于flexible box的几个实例
三列自适应布局,且有固定margin
| 01 | <!DOCTYPE html> | 
| 02 | <html> | 
| 03 | <style> | 
| 04 | .wrap { | 
| 05 |     display: -webkit-box; | 
| 06 |     -webkit-box-orient: horizontal; | 
| 07 | } | 
| 08 | .child { | 
| 09 |     min-height: 200px; | 
| 10 |     border: 2px solid#666; | 
| 11 |     -webkit-box-flex: 1; | 
| 12 |     margin: 10px; | 
| 13 |     font-size: 100px; | 
| 14 |     font-weight: bold; | 
| 15 |     font-family: Georgia; | 
| 16 |     -webkit-box-align: center; | 
| 17 | } | 
| 18 | </style> | 
| 19 | 
| 20 | <div class="wrap"> | 
| 21 | <div class="child">1</div> | 
| 22 | <div class="child">2</div> | 
| 23 | <div class="child">3</div> | 
| 24 | </div> | 
| 25 | </html> | 
当一列定宽,其余两列分配不同比例亦可(三列布局,一列定宽,其余两列按1:2的比例自适应)
| 01 | <!DOCTYPE html> | 
| 02 | <html> | 
| 03 | <meta charset="utf-8"/> | 
| 04 | <style> | 
| 05 | .wrap { | 
| 06 |     display: -webkit-box; | 
| 07 |     -webkit-box-orient: horizontal; | 
| 08 | } | 
| 09 | .child { | 
| 10 |     min-height: 200px; | 
| 11 |     border: 2px solid#666; | 
| 12 |     margin: 10px; | 
| 13 |     font-size: 40px; | 
| 14 |     font-weight: bold; | 
| 15 |     font-family: Georgia; | 
| 16 |     -webkit-box-align: center; | 
| 17 | } | 
| 18 | .w200 {width: 200px} | 
| 19 | .flex1 {-webkit-box-flex: 1} | 
| 20 | .flex2 {-webkit-box-flex: 2} | 
| 21 | </style> | 
| 22 | 
| 23 | <div class="wrap"> | 
| 24 | <div class="child w200">200px</div> | 
| 25 | <div class="child flex1">比例1</div> | 
| 26 | <div class="child flex2">比例2</div> | 
| 27 | </div> | 
| 28 | </html> | 
下面是一个常见的web page 的基本布局
| <style> | 
| header, footer, section { | 
|     border: 10px solid#333; | 
|     font-family: Georgia; | 
|     font-size: 40px; | 
|     text-align: center; | 
|     margin: 10px; | 
| } | 
| #doc { | 
|     width: 80%; | 
|     min-width: 600px; | 
|     height: 100%; | 
|     display: -webkit-box;  | 
|     -webkit-box-orient: vertical; | 
|     margin: 0 auto; | 
| } | 
| header, | 
| footer { | 
|     min-height: 100px; | 
|     -webkit-box-flex: 1; | 
| } | 
| #content { | 
|     min-height: 400px; | 
|     display: -webkit-box; | 
|     -webkit-box-orient: horizontal; | 
| } | 
| .w200 {width: 200px} | 
| .flex1 {-webkit-box-flex: 1} | 
| .flex2 {-webkit-box-flex: 2} | 
| .flex3 {-webkit-box-flex: 3} | 
| </style> | 
| <div id="doc"> | 
|     <header>Header</header> | 
|     <div id="content"> | 
|         <section class="w200">定宽200</section> | 
|         <section class="flex3">比例3</section> | 
|         <section class="flex1">比例1</section> | 
|     </div> | 
|     <footer>Footer</footer> | 
| </div> | 
有了 flexible box 后,横列布局的时候不用计算外围容器和容器里面的元素的宽度。然后再进行横向的滑动的效果就会省去不少麻烦。
| 001 | /** | 
| 002 |  * css3 translate flip | 
| 003 |  * -webkit-box | 
| 004 |  * @author: horizon | 
| 005 |  */ | 
| 006 | 
| 007 | (function(win, undefined) { | 
| 008 |   | 
| 009 |     varinitializing = false, | 
| 010 |         superTest = /horizon/.test(function() {horizon;}) ? /\b_super\b/ : /.*/; | 
| 011 |     this.Class = function() {}; | 
| 012 | 
| 013 |     Class.extend = function(prop) { | 
| 014 |         var_super = this.prototype; | 
| 015 |         initializing = true; | 
| 016 |         varprototype = newthis(); | 
| 017 |         initializing = false; | 
| 018 | 
| 019 |         for(varname inprop) { | 
| 020 |             prototype[name] = (typeofprop[name] === 'function'&& typeof_super[name] === 'function'&& superTest.test(prop[name])) ? (function(name, fn) { | 
| 021 |                     returnfunction() { | 
| 022 |                         vartemp = this._super;  | 
| 023 |                         this._super = _super[name]; | 
| 024 |                         varret = fn.apply(this, arguments); | 
| 025 |                         this._super = temp; | 
| 026 | 
| 027 |                         returnret; | 
| 028 |                     } | 
| 029 |                 })(name, prop[name]) : prop[name]; | 
| 030 |         } | 
| 031 |          | 
| 032 |         functionClass () { | 
| 033 |             if(!initializing && this.init) { | 
| 034 |                 this.init.apply(this, arguments); | 
| 035 |             } | 
| 036 |         } | 
| 037 |         Class.prototype = prototype; | 
| 038 |         Class.constructor = Class; | 
| 039 |         Class.extend = arguments.callee; | 
| 040 | 
| 041 |         returnClass; | 
| 042 |     }; | 
| 043 | 
| 044 |     var$support = { | 
| 045 |         transform3d: ('WebKitCSSMatrix'inwin), | 
| 046 |         touch: ('ontouchstart'inwin) | 
| 047 |     }; | 
| 048 | 
| 049 |     var$E = { | 
| 050 |         start: $support.touch ? 'touchstart': 'mousedown', | 
| 051 |         move: $support.touch ? 'touchmove': 'mousemove', | 
| 052 |         end: $support.touch ? 'touchend': 'mouseup' | 
| 053 |     }; | 
| 054 | 
| 055 |     functiongetTranslate (x) { | 
| 056 |         return$support.transform3d ? 'translate3d('+x+'px, 0, 0)': 'translate('+x+'px, 0)'; | 
| 057 |     } | 
| 058 |     functiongetPage (event, page) { | 
| 059 |         return$support.touch ? event.changedTouches[0][page] : event[page]; | 
| 060 |     } | 
| 061 | 
| 062 | 
| 063 |     varCss3Flip = Class.extend({ | 
| 064 |         init: function(selector, conf) { | 
| 065 |             varself = this; | 
| 066 |              | 
| 067 |             if(selector.nodeType && selector.nodeType == 1) { | 
| 068 |                 self.element = selector; | 
| 069 |             } elseif(typeofselector == 'string') { | 
| 070 |                 self.element = document.getElementById(selector) || document.querySelector(selector); | 
| 071 |             } | 
| 072 |              | 
| 073 |             self.element.style.display = '-webkit-box'; | 
| 074 |             self.element.style.webkitTransitionProperty = '-webkit-transform'; | 
| 075 |             self.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)'; | 
| 076 |             self.element.style.webkitTransitionDuration = '0'; | 
| 077 |             self.element.style.webkitTransform = getTranslate(0); | 
| 078 | 
| 079 |             self.conf = conf || {}; | 
| 080 |             self.touchEnabled = true; | 
| 081 |             self.currentPoint = 0; | 
| 082 |             self.currentX = 0; | 
| 083 | 
| 084 |             self.refresh(); | 
| 085 |              | 
| 086 |             // 支持handleEvent | 
| 087 |             self.element.addEventListener($E.start, self, false); | 
| 088 |             self.element.addEventListener($E.move, self, false); | 
| 089 |             document.addEventListener($E.end, self, false); | 
| 090 | 
| 091 |             returnself; | 
| 092 |              | 
| 093 |         }, | 
| 094 |         handleEvent: function(event) { | 
| 095 |             varself = this; | 
| 096 | 
| 097 |             switch(event.type) { | 
| 098 |                 case$E.start: | 
| 099 |                     self._touchStart(event); | 
| 100 |                     break; | 
| 101 |                 case$E.move: | 
| 102 |                     self._touchMove(event); | 
| 103 |                     break; | 
| 104 |                 case$E.end: | 
| 105 |                     self._touchEnd(event); | 
| 106 |                     break; | 
| 107 |                 case'click': | 
| 108 |                     self._click(event); | 
| 109 |                     break; | 
| 110 |             } | 
| 111 |         }, | 
| 112 |         refresh: function() { | 
| 113 |             varself = this; | 
| 114 | 
| 115 |             varconf = self.conf; | 
| 116 | 
| 117 |             // setting max point | 
| 118 |             self.maxPoint = conf.point || (function() { | 
| 119 |                 varchildNodes = self.element.childNodes, | 
| 120 |                     itemLength = 0, | 
| 121 |                     i = 0, | 
| 122 |                     len = childNodes.length, | 
| 123 |                     node; | 
| 124 |                 for(; i < len; i++) { | 
| 125 |                     node = childNodes[i]; | 
| 126 |                     if(node.nodeType === 1) { | 
| 127 |                         itemLength++; | 
| 128 |                     } | 
| 129 |                 } | 
| 130 |                 if(itemLength > 0) { | 
| 131 |                     itemLength--; | 
| 132 |                 } | 
| 133 |      | 
| 134 |                 returnitemLength; | 
| 135 |             })(); | 
| 136 | 
| 137 |             // setting distance | 
| 138 |             self.distance = conf.distance || self.element.scrollWidth / (self.maxPoint + 1); | 
| 139 | 
| 140 |             // setting maxX | 
| 141 |             self.maxX = conf.maxX ? - conf.maxX : - self.distance * self.maxPoint; | 
| 142 |      | 
| 143 |             self.moveToPoint(self.currentPoint); | 
| 144 |         }, | 
| 145 |         hasNext: function() { | 
| 146 |             varself = this; | 
| 147 |      | 
| 148 |             returnself.currentPoint < self.maxPoint; | 
| 149 |         }, | 
| 150 |         hasPrev: function() { | 
| 151 |             varself = this; | 
| 152 |      | 
| 153 |             returnself.currentPoint > 0; | 
| 154 |         }, | 
| 155 |         toNext: function() { | 
| 156 |             varself = this; | 
| 157 | 
| 158 |             if(!self.hasNext()) { | 
| 159 |                 return; | 
| 160 |             } | 
| 161 | 
| 162 |             self.moveToPoint(self.currentPoint + 1); | 
| 163 |         }, | 
| 164 |         toPrev: function() { | 
| 165 |             varself = this; | 
| 166 | 
| 167 |             if(!self.hasPrev()) { | 
| 168 |                 return; | 
| 169 |             } | 
| 170 | 
| 171 |             self.moveToPoint(self.currentPoint - 1); | 
| 172 |         }, | 
| 173 |         moveToPoint: function(point) { | 
| 174 |             varself = this; | 
| 175 | 
| 176 |             self.currentPoint =  | 
| 177 |                 (point < 0) ? 0 : | 
| 178 |                 (point > self.maxPoint) ? self.maxPoint : | 
| 179 |                 parseInt(point); | 
| 180 | 
| 181 |             self.element.style.webkitTransitionDuration = '500ms'; | 
| 182 |             self._setX(- self.currentPoint * self.distance) | 
| 183 | 
| 184 |             varev = document.createEvent('Event'); | 
| 185 |             ev.initEvent('css3flip.moveend', true, false); | 
| 186 |             self.element.dispatchEvent(ev); | 
| 187 |         }, | 
| 188 |         _setX: function(x) { | 
| 189 |             varself = this; | 
| 190 | 
| 191 |             self.currentX = x; | 
| 192 |             self.element.style.webkitTransform = getTranslate(x); | 
| 193 |         }, | 
| 194 |         _touchStart: function(event) { | 
| 195 |             varself = this; | 
| 196 | 
| 197 |             if(!self.touchEnabled) { | 
| 198 |                 return; | 
| 199 |             } | 
| 200 | 
| 201 |             if(!$support.touch) { | 
| 202 |                 event.preventDefault(); | 
| 203 |             } | 
| 204 | 
| 205 |             self.element.style.webkitTransitionDuration = '0'; | 
| 206 |             self.scrolling = true; | 
| 207 |             self.moveReady = false; | 
| 208 |             self.startPageX = getPage(event, 'pageX'); | 
| 209 |             self.startPageY = getPage(event, 'pageY'); | 
| 210 |             self.basePageX = self.startPageX; | 
| 211 |             self.directionX = 0; | 
| 212 |             self.startTime = event.timeStamp; | 
| 213 |         }, | 
| 214 |         _touchMove: function(event) { | 
| 215 |             varself = this; | 
| 216 | 
| 217 |             if(!self.scrolling) { | 
| 218 |                 return; | 
| 219 |             } | 
| 220 | 
| 221 |             varpageX = getPage(event, 'pageX'), | 
| 222 |                 pageY = getPage(event, 'pageY'), | 
| 223 |                 distX, | 
| 224 |                 newX, | 
| 225 |                 deltaX, | 
| 226 |                 deltaY; | 
| 227 | 
| 228 |             if(self.moveReady) { | 
| 229 |                 event.preventDefault(); | 
| 230 |                 event.stopPropagation(); | 
| 231 | 
| 232 |                 distX = pageX - self.basePageX; | 
| 233 |                 newX = self.currentX + distX; | 
| 234 |                 if(newX >= 0 || newX < self.maxX) { | 
| 235 |                     newX = Math.round(self.currentX + distX / 3); | 
| 236 |                 } | 
| 237 |                 self._setX(newX); | 
| 238 | 
| 239 |                 self.directionX = distX > 0 ? -1 : 1; | 
| 240 |             } | 
| 241 |             else{ | 
| 242 |                 deltaX = Math.abs(pageX - self.startPageX); | 
| 243 |                 deltaY = Math.abs(pageY - self.startPageY); | 
| 244 |                 if(deltaX > 5) { | 
| 245 |                     event.preventDefault(); | 
| 246 |                     event.stopPropagation(); | 
| 247 |                     self.moveReady = true; | 
| 248 |                     self.element.addEventListener('click', self, true); | 
| 249 |                 } | 
| 250 |                 elseif(deltaY > 5) { | 
| 251 |                     self.scrolling = false; | 
| 252 |                 } | 
| 253 |             } | 
| 254 | 
| 255 |             self.basePageX = pageX; | 
| 256 |         }, | 
| 257 |         _touchEnd: function(event) { | 
| 258 |             varself = this; | 
| 259 | 
| 260 |             if(!self.scrolling) { | 
| 261 |                 return; | 
| 262 |             } | 
| 263 | 
| 264 |             self.scrolling = false; | 
| 265 | 
| 266 |             varnewPoint = -self.currentX / self.distance; | 
| 267 |             newPoint = | 
| 268 |                 (self.directionX > 0) ? Math.ceil(newPoint) : | 
| 269 |                 (self.directionX < 0) ? Math.floor(newPoint) : | 
| 270 |                 Math.round(newPoint); | 
| 271 | 
| 272 |             self.moveToPoint(newPoint); | 
| 273 | 
| 274 |             setTimeout(function() { | 
| 275 |                 self.element.removeEventListener('click', self, true); | 
| 276 |             }, 200); | 
| 277 |         }, | 
| 278 |         _click: function(event) { | 
| 279 |             varself = this; | 
| 280 | 
| 281 |             event.stopPropagation(); | 
| 282 |             event.preventDefault(); | 
| 283 |         }, | 
| 284 |         destroy: function() { | 
| 285 |             varself = this; | 
| 286 | 
| 287 |             self.element.removeEventListener(touchStartEvent, self); | 
| 288 |             self.element.removeEventListener(touchMoveEvent, self); | 
| 289 |             document.removeEventListener(touchEndEvent, self); | 
| 290 |         } | 
| 291 |          | 
| 292 |          | 
| 293 |     }); | 
| 294 | 
| 295 |     this.Css3Flip = function(selector, conf) { | 
| 296 |         return(thisinstanceofCss3Flip) ? this.init(selector, conf) : newCss3Flip(selector, conf); | 
| 297 |     } | 
| 298 |      | 
| 299 |   | 
| 300 |  })(window); | 
通过改变translate 而不是改变 left 或者margin-left 来实现滑动,效率提升会很明显,平滑度几乎可以媲美native app。在对js执行效率不是很高的移动终端中尤为明显。
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号