如果說(shuō)從web Pages 能夠轉(zhuǎn)到web app時(shí)代,那么css3和html5其他相關(guān)技術(shù)一定是巨大的功臣。
唯一的遺憾就是pc端瀏覽器的泛濫導(dǎo)致了我們不得不走所謂的優(yōu)雅降級(jí),而且這種降級(jí)是降到新技術(shù)幾乎木有多大的用武之地。
于是,客戶端還算統(tǒng)一的移動(dòng)端開始成了一個(gè)大的試驗(yàn)田。能夠讓眾人大肆的在上面舒展拳腳。諸如眾多新起的ui庫(kù)或者框架(jquery-mobile, sencha, phoneGap ...),可見在移動(dòng)終端上確實(shí)還有不小的田地??v使如此,效率仍舊成為一個(gè)最大的瓶頸。
之前有一種嘗試是用CSS3的transfrom或者animation給一個(gè)duration和ease的屬性來(lái)做動(dòng)畫,這樣不管改變?nèi)魏蝧tyle樣式,都會(huì)根據(jù)這個(gè)ease有緩動(dòng)的效果。
例如:
/* webkit */
-webkit-transition-duration: 500ms;
在webkit內(nèi)核瀏覽器下,只要有這個(gè)屬性,再去改變這個(gè)元素任何的樣式,它都會(huì)以一個(gè)默認(rèn)的緩動(dòng)效果完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | /** * CSS3 animation by transform * @example * Let(el) * .to(500, 200) * .rotate(180) * .scale(.5) * .set({ * background-color: 'red', * border-color: 'green' * }) * .duration(2000) * .skew(50, -10) * .then() * .set('opacity', .5) * .duration('1s') * .scale(1) * .pop() * .end(); */ ( function (win, undefined) { var initializing = false , superTest = /horizon/.test( function () {horizon;}) ? /\b_super\b/ : /.*/; // 臨時(shí)Class this .Class = function () {}; // 繼承方法extend Class.extend = function (prop) { var _super = this .prototype; //創(chuàng)建一個(gè)實(shí)例,但不執(zhí)行init initializing = true ; var prototype = new this (); initializing = false ; for ( var name in prop) { // 用閉包保證多級(jí)繼承不會(huì)污染 prototype[name] = ( typeof prop[name] === 'function' && typeof _super[name] === 'function' && superTest.test(prop[name])) ? ( function (name, fn) { return function () { var temp = this ._super; // 當(dāng)前子類通過(guò)_super繼承父類 this ._super = _super[name]; //繼承方法執(zhí)行完畢后還原 var ret = fn.apply( this , arguments); this ._super = temp; return ret; } })(name, prop[name]) : prop[name]; } //真實(shí)的constructor function Class () { if (!initializing && this .init) { this .init.apply( this , arguments); } } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class; } // 樣式為數(shù)字+px 的屬性 var map = { 'top' : 'px' , 'left' : 'px' , 'right' : 'px' , 'bottom' : 'px' , 'width' : 'px' , 'height' : 'px' , 'font-size' : 'px' , 'margin' : 'px' , 'margin-top' : 'px' , 'margin-left' : 'px' , 'margin-right' : 'px' , 'margin-bottom' : 'px' , 'padding' : 'px' , 'padding-left' : 'px' , 'padding-right' : 'px' , 'padding-top' : 'px' , 'padding-bottom' : 'px' , 'border-width' : 'px' }; /** * Let package */ var Let = function (selector) { var el = Let.G(selector); return new Anim(el); }; Let.defaults = { duration: 500 }; Let.ease = { 'in' : 'ease-in' , 'out' : 'ease-out' , 'in-out' : 'ease-in-out' , 'snap' : 'cubic-bezier(0,1,.5,1)' }; Let.G = function (selector) { if ( typeof selector != 'string' && selector.nodeType == 1) { return selector; } return document.getElementById(selector) || document.querySelectorAll(selector)[0]; }; /** * EventEmitter * {Class} */ var EventEmitter = Class.extend({ init: function () { this .callbacks = {}; }, on: function (event, fn) { ( this .callbacks[event] = this .callbacks[event] || []).push(fn); return this ; }, /** * param {event} 指定event * params 指定event的callback的參數(shù) */ fire: function (event) { var args = Array.prototype.slice.call(arguments, 1), callbacks = this .callbacks[event], len; if (callbacks) { for ( var i = 0, len = callbacks.length; i < len; i ++) { callbacks[i].apply( this , args); } } return this ; } }); /** * Anim * {Class} * @inherit from EventEmitter */ var Anim = EventEmitter.extend({ init: function (el) { this ._super(); if (!( this instanceof Anim)) { return new Anim(el); } this .el = el; this ._props = {}; this ._rotate = 0; this ._transitionProps = []; this ._transforms = []; this .duration(Let.defaults.duration); }, transform : function (transform) { this ._transforms.push(transform); return this ; }, // skew methods skew: function (x, y) { y = y || 0; return this .transform( 'skew(' + x + 'deg, ' + y + 'deg)' ); }, skewX: function (x) { return this .transform( 'skewX(' + x + 'deg)' ); }, skewY: function (y) { return this .transform( 'skewY(' + y + 'deg)' ); }, // translate methods translate: function (x, y) { y = y || 0; return this .transform( 'translate(' + x + 'px, ' + y + 'px)' ); }, to: function (x, y) { return this .translate(x, y); }, translateX: function (x) { return this .transform( 'translateX(' + x + 'px)' ); }, x: function (x) { return this .translateX(x); }, translateY: function (y) { return this .transform( 'translateY(' + y + 'px)' ); }, y: function (y) { return this .translateY(y); }, // scale methods scale: function (x, y) { y = (y == null ) ? x : y; return this .transform( 'scale(' + x + ', ' + y + ')' ); }, scaleX: function (x) { return this .transform( 'scaleX(' + x + ')' ); }, scaleY: function (y) { return this .transform( 'scaleY(' + y + ')' ); }, // rotate methods rotate: function (n) { return this .transform( 'rotate(' + n + 'deg)' ); }, // set transition ease ease: function (fn) { fn = Let.ease[fn] || fn || 'ease' ; return this .setVendorProperty( 'transition-timing-function' , fn); }, //set duration time duration: function (n) { n = this ._duration = ( typeof n == 'string' ) ? parseFloat(n)*1000 : n; return this .setVendorProperty( 'transition-duration' , n + 'ms' ); }, // set delay time delay: function (n) { n = ( typeof n == 'string' ) ? parseFloat(n) * 1000 : n; return this .setVendorProperty( 'transition-delay' , n + 'ms' ); }, // set property to val setProperty: function (prop, val) { this ._props[prop] = val; return this ; }, setVendorProperty: function (prop, val) { this .setProperty( '-webkit-' + prop, val); this .setProperty( '-moz-' + prop, val); this .setProperty( '-ms-' + prop, val); this .setProperty( '-o-' + prop, val); return this ; }, set: function (prop, val) { var _store = {}; if ( typeof prop == 'string' && val != undefined) { _store[prop] = val; } else if ( typeof prop == 'object' && prop.constructor.prototype.hasOwnProperty( 'hasOwnProperty' )) { _store = prop; } for ( var key in _store) { this .transition(key); if ( typeof _store[key] == 'number' && map[key]) { _store[key] += map[key]; } this ._props[key] = _store[key]; } return this ; }, // add value to a property add: function (prop, val) { var self = this ; return this .on( 'start' , function () { var curr = parseInt(self.current(prop), 10); self.set(prop, curr + val + 'px' ); }) }, // sub value to a property sub: function (prop, val) { var self = this ; return this .on( 'start' , function () { var curr = parseInt(self.current(prop), 10); self.set(prop, curr - val + 'px' ); }) }, current: function (prop) { return !!window.getComputedStyle ? document.defaultView.getComputedStyle( this .el, null ).getPropertyValue(prop) : this .el.currentStyle(prop); }, transition: function (prop) { for ( var i = 0; i < this ._transitionProps.length; i ++) { if ( this ._transitionProps[i] == prop) { return this ; } } this ._transitionProps.push(prop); return this ; }, applyPropertys: function () { var props = this ._props, el = this .el; for ( var prop in props) { if (props.hasOwnProperty(prop)) { el.style.setProperty ? el.style.setProperty(prop, props[prop], '' ) : el.style[prop] = props[prop]; } } return this ; }, // then then: function (fn) { if (fn instanceof Anim) { this .on( 'end' , function () { fn.end(); }) } else if ( typeof fn == 'function' ) { this .on( 'end' , fn); } else { var clone = new Anim( this .el); clone._transforms = this ._transforms.slice(0); this .then(clone); clone.parent = this ; return clone; } return this ; }, pop: function () { return this .parent; }, end: function (fn) { var self = this ; this .fire( 'start' ); if ( this ._transforms.length > 0) { this .setVendorProperty( 'transform' , this ._transforms.join( ' ' )); } this .setVendorProperty( 'transition-properties' , this ._transitionProps.join( ', ' )); this .applyPropertys(); if (fn) { this .then(fn) } setTimeout( function () { self.fire( 'end' ); }, this ._duration); return this ; } }); this .Let = win.Let = Let; })(window) |
比如下面代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <div id= "test" ></div> <script> Let( '#test' ) .to(200, 200) .rotate(1000) .scale(.5) .set({ 'background-color' : 'red' , 'width' : 300 }) .duration(2000) .then() .set( 'opacity' , .5) .set( 'height' , 200) .duration( '1s' ) .scale(1.5) .to(300, 300) .pop() .end() </script> |
這樣子有好處是可以針對(duì)所有的style樣式。所以可以用同樣的方式來(lái)對(duì) left, top,margin-left,margin-top 之類的css2 的style屬性來(lái)完成dom的相應(yīng)變化。
但是,其實(shí),用transform或者animation來(lái)操作css2的style屬性。效率依然不高。在當(dāng)前的移動(dòng)終端,ipad還ok(畢竟是喬幫主的產(chǎn)品),iphone和android pad上執(zhí)行效率在大部分情況下很難達(dá)到優(yōu)秀app所要求的體驗(yàn)。
所以要做滑動(dòng)之類的改變dom位置的體驗(yàn)。更好的實(shí)現(xiàn)應(yīng)該是用純粹的translate來(lái)改變位置,為了更好的與之配合,布局就尤為重要。
下面看看webkit提供的 display:-webkit-box; 亦即
我稱其為【流體盒模型】
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).
偶英文蹩腳,就不翻譯了,用另外一番話來(lái)看它的意思:
1.之前要實(shí)現(xiàn)橫列的web布局,通常就是float或者display:inline-block; 但是都不能做到真正的流體布局。至少width要自己去算百分比。
2.flexible box 就可以實(shí)現(xiàn)真正意義上的流體布局。只要給出相應(yīng)屬性,瀏覽器會(huì)幫我們做額外的計(jì)算。
提供的關(guān)于盒模型的幾個(gè)屬性:
box-orient 子元素排列 vertical or horizontal box-flex 兄弟元素之間比例,僅作一個(gè)系數(shù) box-align box 排列 box-direction box 方向 box-flex-group 以組為單位的流體系數(shù) box-lines box-ordinal-group 以組為單位的子元素排列方向 box-pack |
以下是關(guān)于flexible box的幾個(gè)實(shí)例
三列自適應(yīng)布局,且有固定margin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!DOCTYPE html> <html> <style> .wrap { display: -webkit-box; -webkit-box-orient: horizontal; } .child { min-height: 200px; border: 2px solid #666; -webkit-box-flex: 1; margin: 10px; font-size: 100px; font-weight: bold; font-family: Georgia; -webkit-box-align: center; } </style> <div class= "wrap" > <div class= "child" >1</div> <div class= "child" >2</div> <div class= "child" >3</div> </div> </html> |
當(dāng)一列定寬,其余兩列分配不同比例亦可(三列布局,一列定寬,其余兩列按1:2的比例自適應(yīng))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!DOCTYPE html> <html> <meta charset= "utf-8" /> <style> .wrap { display: -webkit-box; -webkit-box-orient: horizontal; } .child { min-height: 200px; border: 2px solid #666; margin: 10px; font-size: 40px; font-weight: bold; font-family: Georgia; -webkit-box-align: center; } .w200 {width: 200px} .flex1 {-webkit-box-flex: 1} .flex2 {-webkit-box-flex: 2} </style> <div class= "wrap" > <div class= "child w200" >200px</div> <div class= "child flex1" >比例1</div> <div class= "child flex2" >比例2</div> </div> </html> |
下面是一個(gè)常見的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 后,橫列布局的時(shí)候不用計(jì)算外圍容器和容器里面的元素的寬度。然后再進(jìn)行橫向的滑動(dòng)的效果就會(huì)省去不少麻煩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | /** * css3 translate flip * -webkit-box * @author: horizon */ ( function (win, undefined) { var initializing = false , superTest = /horizon/.test( function () {horizon;}) ? /\b_super\b/ : /.*/; this .Class = function () {}; Class.extend = function (prop) { var _super = this .prototype; initializing = true ; var prototype = new this (); initializing = false ; for ( var name in prop) { prototype[name] = ( typeof prop[name] === 'function' && typeof _super[name] === 'function' && superTest.test(prop[name])) ? ( function (name, fn) { return function () { var temp = this ._super; this ._super = _super[name]; var ret = fn.apply( this , arguments); this ._super = temp; return ret; } })(name, prop[name]) : prop[name]; } function Class () { if (!initializing && this .init) { this .init.apply( this , arguments); } } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class; }; var $support = { transform3d: ( 'WebKitCSSMatrix' in win), touch: ( 'ontouchstart' in win) }; var $E = { start: $support.touch ? 'touchstart' : 'mousedown' , move: $support.touch ? 'touchmove' : 'mousemove' , end: $support.touch ? 'touchend' : 'mouseup' }; function getTranslate (x) { return $support.transform3d ? 'translate3d(' +x+ 'px, 0, 0)' : 'translate(' +x+ 'px, 0)' ; } function getPage (event, page) { return $support.touch ? event.changedTouches[0][page] : event[page]; } var Css3Flip = Class.extend({ init: function (selector, conf) { var self = this ; if (selector.nodeType && selector.nodeType == 1) { self.element = selector; } else if ( typeof selector == 'string' ) { self.element = document.getElementById(selector) || document.querySelector(selector); } self.element.style.display = '-webkit-box' ; self.element.style.webkitTransitionProperty = '-webkit-transform' ; self.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)' ; self.element.style.webkitTransitionDuration = '0' ; self.element.style.webkitTransform = getTranslate(0); self.conf = conf || {}; self.touchEnabled = true ; self.currentPoint = 0; self.currentX = 0; self.refresh(); // 支持handleEvent self.element.addEventListener($E.start, self, false ); self.element.addEventListener($E.move, self, false ); document.addEventListener($E.end, self, false ); return self; }, handleEvent: function (event) { var self = this ; switch (event.type) { case $E.start: self._touchStart(event); break ; case $E.move: self._touchMove(event); break ; case $E.end: self._touchEnd(event); break ; case 'click' : self._click(event); break ; } }, refresh: function () { var self = this ; var conf = self.conf; // setting max point self.maxPoint = conf.point || ( function () { var childNodes = self.element.childNodes, itemLength = 0, i = 0, len = childNodes.length, node; for (; i < len; i++) { node = childNodes[i]; if (node.nodeType === 1) { itemLength++; } } if (itemLength > 0) { itemLength--; } return itemLength; })(); // setting distance self.distance = conf.distance || self.element.scrollWidth / (self.maxPoint + 1); // setting maxX self.maxX = conf.maxX ? - conf.maxX : - self.distance * self.maxPoint; self.moveToPoint(self.currentPoint); }, hasNext: function () { var self = this ; return self.currentPoint < self.maxPoint; }, hasPrev: function () { var self = this ; return self.currentPoint > 0; }, toNext: function () { var self = this ; if (!self.hasNext()) { return ; } self.moveToPoint(self.currentPoint + 1); }, toPrev: function () { var self = this ; if (!self.hasPrev()) { return ; } self.moveToPoint(self.currentPoint - 1); }, moveToPoint: function (point) { var self = this ; self.currentPoint = (point < 0) ? 0 : (point > self.maxPoint) ? self.maxPoint : parseInt(point); self.element.style.webkitTransitionDuration = '500ms' ; self._setX(- self.currentPoint * self.distance) var ev = document.createEvent( 'Event' ); ev.initEvent( 'css3flip.moveend' , true , false ); self.element.dispatchEvent(ev); }, _setX: function (x) { var self = this ; self.currentX = x; self.element.style.webkitTransform = getTranslate(x); }, _touchStart: function (event) { var self = this ; if (!self.touchEnabled) { return ; } if (!$support.touch) { event.preventDefault(); } self.element.style.webkitTransitionDuration = '0' ; self.scrolling = true ; self.moveReady = false ; self.startPageX = getPage(event, 'pageX' ); self.startPageY = getPage(event, 'pageY' ); self.basePageX = self.startPageX; self.directionX = 0; self.startTime = event.timeStamp; }, _touchMove: function (event) { var self = this ; if (!self.scrolling) { return ; } var pageX = getPage(event, 'pageX' ), pageY = getPage(event, 'pageY' ), distX, newX, deltaX, deltaY; if (self.moveReady) { event.preventDefault(); event.stopPropagation(); distX = pageX - self.basePageX; newX = self.currentX + distX; if (newX >= 0 || newX < self.maxX) { newX = Math.round(self.currentX + distX / 3); } self._setX(newX); self.directionX = distX > 0 ? -1 : 1; } else { deltaX = Math.abs(pageX - self.startPageX); deltaY = Math.abs(pageY - self.startPageY); if (deltaX > 5) { event.preventDefault(); event.stopPropagation(); self.moveReady = true ; self.element.addEventListener( 'click' , self, true ); } else if (deltaY > 5) { self.scrolling = false ; } } self.basePageX = pageX; }, _touchEnd: function (event) { var self = this ; if (!self.scrolling) { return ; } self.scrolling = false ; var newPoint = -self.currentX / self.distance; newPoint = (self.directionX > 0) ? Math.ceil(newPoint) : (self.directionX < 0) ? Math.floor(newPoint) : Math.round(newPoint); self.moveToPoint(newPoint); setTimeout( function () { self.element.removeEventListener( 'click' , self, true ); }, 200); }, _click: function (event) { var self = this ; event.stopPropagation(); event.preventDefault(); }, destroy: function () { var self = this ; self.element.removeEventListener(touchStartEvent, self); self.element.removeEventListener(touchMoveEvent, self); document.removeEventListener(touchEndEvent, self); } }); this .Css3Flip = function (selector, conf) { return ( this instanceof Css3Flip) ? this .init(selector, conf) : new Css3Flip(selector, conf); } })( |
聯(lián)系客服