├── MIT-LICENSE.txt ├── README.md ├── images └── pager6x6.png ├── jquery.mobile.flip.css ├── jquery.mobile.flip.js ├── jquery.mobile.flip.min.css ├── jquery.mobile.flip.min.js ├── jquery.mobile.flip.min.js.map └── tests ├── flipTest.js └── test_flip.html /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Hiro Inami, http://amegan.com/jquery-flip/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Flip Plugin 2 | 3 | jQuery/jQuery mobile plugin to give Flipboard app like effect. Flip effect uses css 3d transform. Flip effect currently works on WebKit browsers (e.g. Chrome, Safari, including iOS mobile safari) or Firefox 11. It still works with other browsers but the "slide" effect will be selected forecely. 4 | 5 | ## Compatibility 6 | Current version is compatible with jQuery 1.6.4, 1.7.X, 1.8.X and jQuery Mobile 1.0, 1.1 and 1.2. 7 | 8 | ## Screenshots 9 | ![Flip right-to-left](http://amegan.github.com/jquery-flip/shot.png "Flip Right-to-Left") ![Flip bottom-to-top](http://amegan.github.com/jquery-flip/shot2.png "Flip Bottom-to-Top") 10 | 11 | ## Installation 12 | 13 | Copy jquery.mobile.flip.js, jquery.mobile.flip.css and images directory to your web page project. Note that css file and images folder must be in the same directory. 14 | 15 | After copying files to your web project, load js and css file into your html. 16 | 17 | 18 | 19 | 20 | 21 | ## github page 22 | [http://amegan.github.com/jquery-flip/](http://amegan.github.com/jquery-flip/ "jquery-flip on github page") 23 | 24 | ## Demo 25 | [http://amegan.github.com/jquery-flip/demo/instagram.html](http://amegan.github.com/jquery-flip/demo/instagram.html "Instagram demo") 26 | 27 | 28 | ## Usage 29 | 30 | ### Prerequisite 31 | This plugin expects nested `
`, `

`, `

` or `
` elements structure. Parent element of them will be used to initialize plugin. 32 | 33 |
34 | 35 |
36 | Flip Content 1 37 |
38 | 39 |

40 | Flip Content 2 41 |

42 | 43 | 44 | Flip Content 3 45 |
46 | 47 |
48 |

Flip Content 4

49 |

You can put any elements under here

50 |
51 |
52 | 53 | ### jQuery User 54 | jQuery user can enable plugin by calling jQuery.flip() method. 55 | 56 | $(document).ready(function() { 57 | $("#flipRoot").flip(); 58 | }); 59 | 60 | option object can be passed to the flip() method. Available options are described later. 61 | 62 | $(document).ready(function() { 63 | $("#flipRoot").flip({ 64 | forwardDir: "ltor", 65 | height: "340px", 66 | showpager: true, 67 | loop: true})); 68 | }); 69 | 70 | ### jQuery Mobile User 71 | Plugin will be initialized with the element which has data-role="flip" attribute without calling initialization method. 72 | 73 |
74 |
75 | Flip Content 1 76 |
77 |

78 | Flip Content 2 79 |

80 |
81 | Flip Content 3 82 |
83 |
84 | 85 | Option can be passed through data-flip- prefix attribute too. 86 | 87 |
88 |
89 | Flip Content 1 90 |
91 |

92 | Flip Content 2 93 |

94 |
95 | Flip Content 3 96 |
97 |
98 | 99 | ## Options 100 | Following option is supported. 101 | 102 | option name | description | jqm attribute | value 103 | -------------|-------------|---------------|------ 104 | effect |Transiton effect |data-flip-effect|'flip' or 'slide' 105 | forwardDir |forward direction|data-flip-forward-dir|'rtol' or 'ltor' or 'ttob' or 'btot' 106 | height |Content height |data-flip-height| height css (e.g. 300px or 2in) 107 | keyboardNav |enable keyboard navigation|data-flip-keyboard-nav|true or false 108 | showPager |show pager |data-flip-show-pager|true or false 109 | loop |loop contents |data-flip-loop|true or false 110 | 111 | Sample: 112 | 113 | $(document).ready(function() { 114 | $("#flipRoot").flip({ 115 | forwardDir: 'ltor', 116 | height: '340px', 117 | showpager: true, 118 | loop: true})); 119 | }); 120 | 121 |
122 |
123 | Flip Content 1 124 |
125 |

126 | Flip Content 2 127 |

128 |
129 | Flip Content 3 130 |
131 |
132 | 133 | 134 | ## License 135 | 136 | [The MIT License](http://www.opensource.org/licenses/mit-license.php "link to Open Source Initiative") 137 | -------------------------------------------------------------------------------- /images/pager6x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amegan/jquery-flip/9734da3263a5c6188614738681f5efb13e54bc7e/images/pager6x6.png -------------------------------------------------------------------------------- /jquery.mobile.flip.css: -------------------------------------------------------------------------------- 1 | 2 | .flipContainer { 3 | position: relative; 4 | -webkit-perspective: 3000px; 5 | -moz-perspective: 3000px; 6 | perspective: 3000px; 7 | 8 | -webkit-user-select: none; 9 | -moz-user-select: none; 10 | user-select: none; 11 | } 12 | 13 | .flipContent { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | height: 100%; 18 | width: 100%; 19 | display: none; 20 | overflow: hidden; 21 | } 22 | 23 | .flipContent.flipCurrent { 24 | display: block; 25 | } 26 | 27 | .sliding, .slidingBg { 28 | position: absolute; 29 | overflow: hidden; 30 | z-index: 1; 31 | background-color: inherit; 32 | } 33 | 34 | .flipping { 35 | background-color: inherit; 36 | -webkit-backface-visibility: hidden; 37 | -webkit-transform-style: flat; 38 | -webkit-transform: rotateY(0deg); 39 | -moz-backface-visibility: hidden; 40 | -moz-transform-style: preserve3d; 41 | -moz-transform: rotateY(0deg); 42 | backface-visibility: hidden; 43 | transform-style: preserve3d; 44 | transform: rotateY(0deg); 45 | } 46 | 47 | .flipping.firstHalf { 48 | -webkit-transform-origin: 100% 0; 49 | -moz-transform-origin: 100% 0; 50 | transform-origin: 100% 0; 51 | } 52 | 53 | .flipping.secondHalf { 54 | -webkit-transform-origin: 0 0; 55 | -moz-transform-origin: 0 0; 56 | transform-origin: 0 0; 57 | } 58 | 59 | .backflipping { 60 | display: none; /* hidden by default */ 61 | background-color: inherit; 62 | -webkit-backface-visibility: hidden; 63 | -webkit-transform-style:flat; 64 | -webkit-transform: rotateY(180deg); 65 | -moz-backface-visibility: hidden; 66 | -moz-transform-style:flat; 67 | -moz-transform: rotateY(180deg); 68 | backface-visibility: hidden; 69 | transform-style:flat; 70 | transform: rotateY(180deg); 71 | } 72 | 73 | .holizontalFlipping.firstHalf { 74 | -webkit-transform-origin: 100% 0; 75 | -moz-transform-origin: 100% 0; 76 | transform-origin: 100% 0; 77 | } 78 | 79 | .holizontalFlipping.secondHalf { 80 | -webkit-transform-origin: 0 0; 81 | -moz-transform-origin: 0 0; 82 | transform-origin: 0 0; 83 | } 84 | 85 | .verticalFlipping.firstHalf { 86 | -webkit-transform-origin: 0 100%; 87 | -moz-transform-origin: 0 100%; 88 | transform-origin: 0 100%; 89 | } 90 | 91 | .verticalFlipping.secondHalf { 92 | -webkit-transform-origin: 0 0; 93 | -moz-transform-origin: 0 0; 94 | transform-origin: 0 0; 95 | } 96 | 97 | .splitHalf { 98 | position: absolute; 99 | overflow: hidden; 100 | } 101 | 102 | .splitEmpty { 103 | background-color: #333333; 104 | } 105 | 106 | /* pager */ 107 | .flipContainer .pager { 108 | text-align:center; 109 | height: 24px; 110 | line-height: 6px; 111 | position: absolute; 112 | bottom: 0px; 113 | width: 100%; 114 | cursor: pointer; 115 | font-size: 6px; 116 | overflow: hidden; 117 | } 118 | 119 | .flipContainer .pager span.dot { 120 | width: 6px; 121 | display: inline-block; 122 | } 123 | 124 | .flipContainer .pager span.dot { 125 | text-decoration: none; 126 | margin: 8px 5px; 127 | background-image: url('images/pager6x6.png'); 128 | background-repeat: no-repeat; 129 | background-position: 0 -6px; 130 | } 131 | 132 | .flipContainer .pager span.dot.current { 133 | background-position: 0 0; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /jquery.mobile.flip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: Flip 3 | * Description: Flip to change the contents 4 | * Author: Hiro Inami 5 | * License: MIT License 6 | */ 7 | 8 | 9 | // the semi-colon before function invocation is a safety net against 10 | // sconcatenated cripts and/or other plugins which may not be closed properly. 11 | ;(function ($, window, document, undefined) { 12 | 13 | // undefined is used here as the undefined global variable in ECMAScript 3 is 14 | // mutable (ie. it can be changed by someone else). undefined isn't really 15 | // being passed in so we can ensure the value of it is truly undefined. 16 | // In ES5, undefined can no longer be modified. 17 | 18 | // window and document are passed through as local variables rather than 19 | // globals as this (slightly) quickens the resolution process and can be 20 | // more efficiently minified (especially when both are regularly referenced 21 | // in your plugin). 22 | // 23 | var DIR_RTOL = 'rtol'; 24 | var DIR_LTOR = 'ltor'; 25 | var DIR_TTOB = 'ttob'; 26 | var DIR_BTOT = 'btot'; 27 | 28 | // Create the defaults once 29 | var pluginName = 'flip', 30 | defaults = { 31 | effect: 'flip', // "flip" or "slide" 32 | // (slide will be selected for non-3d 33 | // transition supported browser) 34 | forwardDir: DIR_RTOL, // "ltor" | "rtol" | "ttob" | "btot" 35 | height: '', // height string "" means auto 36 | keyboardNav: true, // flag to do support keyboard naviation 37 | showPager: false, // flag to show pager 38 | loop: false // flag for loop conte 39 | }; 40 | 41 | var _PREFIX_DATA = 'data-'; 42 | var _NS = 'flip-'; 43 | 44 | var TYPE_TEXT = 0; 45 | var TYPE_BOOL = 1; 46 | 47 | var JQM_ATTRS = [ 48 | {key: 'effect', attr: 'effect', type: TYPE_TEXT}, 49 | {key: 'forwardDir', attr: 'forward-dir', type: TYPE_TEXT}, 50 | {key: 'height', attr: 'height', type: TYPE_TEXT}, 51 | {key: 'keyboardNav', attr: 'keyboard-nav', type: TYPE_BOOL}, 52 | {key: 'showPager', attr: 'show-pager', type: TYPE_BOOL}, 53 | {key: 'loop', attr: 'loop', type: TYPE_BOOL} 54 | ]; 55 | 56 | var _FIRST_HALF = 1; 57 | var _SECOND_HALF = -1; 58 | 59 | var CSSPREFIX = ''; 60 | var KEY_LEFT = 37; 61 | var KEY_RIGHT = 39; 62 | 63 | var CURRENT = 'flipCurrent'; 64 | var CLASS_CURRENT = '.'+CURRENT; 65 | 66 | var FLIPPING_BASE_ZINDEX = 10000; 67 | 68 | function Flip(element, options) { 69 | this.START_OFFSET = 8; 70 | this.MAX_DISTANCE = 250; 71 | this.THREASHOLD_RATIO = 0.5; 72 | 73 | this.element = element; 74 | this.options = options; 75 | 76 | this.flippingSide = null; 77 | this.currentRatio = null; 78 | 79 | this.isInAnimation = false; 80 | 81 | this.flippingElement = null; 82 | this.backFilippingElement = null; 83 | } 84 | 85 | Flip.prototype._initShadow = function() { 86 | var $elem = $(this.element); 87 | var $cur = $elem.children(CLASS_CURRENT); 88 | var elemWidth = $cur.width(); 89 | var elemHeight = $cur.height(); 90 | 91 | var $flipShadow = $('
').hide(); 92 | $flipShadow.css('position', 'absolute') 93 | .css('background-color', '#111111') 94 | .css('opacity', '0.9') 95 | .css('display', 'none') 96 | .css('zIndex', FLIPPING_BASE_ZINDEX); 97 | 98 | if (this._isVertical()) { 99 | $flipShadow 100 | .css('width', (elemWidth) + 'px') 101 | .css('height', (elemHeight / 2) + 'px') 102 | .css('top', (elemHeight / 2) + 'px') 103 | .css('left', '0'); 104 | 105 | } else { 106 | $flipShadow 107 | .css('width', (elemWidth / 2) + 'px') 108 | .css('height', elemHeight + 'px') 109 | .css('left', (elemWidth / 2) + 'px') 110 | .css('top', '0'); 111 | } 112 | 113 | return $flipShadow; 114 | }; 115 | 116 | Flip.prototype._isVertical = function() { 117 | return (this.options.forwardDir === DIR_TTOB || 118 | this.options.forwardDir === DIR_BTOT) ? true : false; 119 | } 120 | 121 | Flip.prototype._splitElem = function($elem, customClass) { 122 | 123 | var $flipRoot = $(this.element); 124 | if ($elem === null || $elem.length === 0) { 125 | var rootWidth = $flipRoot.width(); 126 | var rootHeight = $flipRoot.height(); 127 | 128 | if (this.options.showPager) { 129 | var $pager = $flipRoot.children('.pager'); 130 | rootHeight -= $pager.height(); 131 | } 132 | 133 | $elem = $('
'); 135 | } 136 | 137 | var isVertical = this._isVertical(); 138 | 139 | var firstCustom = customClass + 'First'; 140 | var secondCustom = customClass + 'Second'; 141 | 142 | var $firstHalf = $flipRoot.children(firstCustom); 143 | if ($firstHalf.length === 0) { 144 | $firstHalf = $('
').css('zIndex', FLIPPING_BASE_ZINDEX); 146 | } else { 147 | $firstHalf.empty(); 148 | } 149 | 150 | var $secondHalf = $flipRoot.children(secondCustom); 151 | if ($secondHalf.length === 0) { 152 | $secondHalf = $('
').css('zIndex', FLIPPING_BASE_ZINDEX); 154 | } else { 155 | $secondHalf.empty(); 156 | } 157 | 158 | // clone object 159 | $firstHalf.append($elem.clone()); 160 | $secondHalf.append($elem.clone()); 161 | 162 | // cancel anchor click in the flipping element 163 | // should be find because flip effect is working here 164 | $firstHalf.find('a').each(function(idx, anchor) { 165 | $(anchor).bind("click", function(event) { 166 | return false; 167 | }); 168 | }); 169 | 170 | $secondHalf.find('a').each(function(idx, anchor) { 171 | $(anchor).bind("click", function(event) { 172 | return false; 173 | }); 174 | }); 175 | 176 | // adjust widht/height 177 | var elemWidth = $elem.width(); 178 | var elemHeight = $elem.height(); 179 | 180 | // for first half element 181 | if (isVertical) { 182 | $firstHalf 183 | .css('width', elemWidth + 'px') 184 | .css('height', Math.ceil(elemHeight / 2) + 'px'); 185 | 186 | $firstHalf.children(':first') 187 | .css('height', elemHeight + 'px') 188 | .css('display', 'block'); 189 | } else { 190 | $firstHalf 191 | .css('width', Math.ceil(elemWidth / 2) + 'px') 192 | .css('height', elemHeight + 'px'); 193 | 194 | $firstHalf.children(':first') 195 | .css('width', elemWidth + 'px') 196 | .css('display', 'block'); 197 | } 198 | 199 | 200 | // for second half element 201 | if (isVertical) { 202 | $secondHalf 203 | .css('width', elemWidth + 'px') 204 | .css('height', Math.ceil(elemHeight / 2) + 'px') 205 | .css('top', Math.ceil(elemHeight / 2) + 'px'); 206 | 207 | $secondHalf.children(':first') 208 | .css('height', elemHeight + 'px') 209 | .css('display', 'block') 210 | .css('top', -1 * Math.ceil(elemHeight / 2) + 'px'); 211 | } else { 212 | $secondHalf 213 | .css('width', Math.ceil(elemWidth / 2) + 'px') 214 | .css('height', elemHeight + 'px') 215 | .css('left', Math.ceil(elemWidth / 2) + 'px'); 216 | 217 | $secondHalf.children(':first') 218 | .css('width', elemWidth + 'px') 219 | .css('display', 'block') 220 | .css('left', -1 * Math.ceil(elemWidth / 2) + 'px'); 221 | } 222 | 223 | if ($flipRoot.children(firstCustom).length === 0) { 224 | $flipRoot.append($firstHalf); 225 | } 226 | 227 | if ($flipRoot.children(secondCustom).length === 0) { 228 | $flipRoot.append($secondHalf); 229 | } 230 | 231 | return {first: $firstHalf, second: $secondHalf}; 232 | } 233 | 234 | Flip.prototype.init = function(flippingSide) { 235 | // this.currentRatio must be null 236 | if (this.currentRatio !== null) { 237 | return; 238 | } 239 | 240 | // init currentRatio value 241 | this.currentRatio = 0; 242 | this.flippingSide = flippingSide; 243 | 244 | var $elem = $(this.element); 245 | 246 | // find currently shown element 247 | var $cur = $elem.children(CLASS_CURRENT); 248 | var $underContent = null; 249 | var $flippingElem = null 250 | var $flippingBackElem = null; 251 | 252 | if (this.options.forwardDir === DIR_LTOR && flippingSide === _FIRST_HALF || 253 | this.options.forwardDir === DIR_RTOL && flippingSide === _SECOND_HALF || 254 | this.options.forwardDir === DIR_TTOB && flippingSide === _FIRST_HALF || 255 | this.options.forwardDir === DIR_BTOT && flippingSide === _SECOND_HALF) { 256 | $underContent = this.getNextContent($cur); 257 | } else { 258 | $underContent = this.getPrevContent($cur); 259 | } 260 | 261 | var back = this._splitElem($underContent, 'back'); 262 | var front = this._splitElem($cur, 'front'); 263 | 264 | if (flippingSide === _SECOND_HALF) { 265 | $flippingElem = front.second; 266 | $flippingBackElem = back.first; 267 | } else { 268 | $flippingElem = front.first; 269 | $flippingBackElem = back.second; 270 | } 271 | 272 | this.flippingElement = $flippingElem; 273 | this.backFilippingElement = $flippingBackElem; 274 | 275 | // set zIndex 276 | $flippingElem.css('zIndex', FLIPPING_BASE_ZINDEX+1); 277 | $flippingBackElem.css('zIndex', FLIPPING_BASE_ZINDEX); 278 | 279 | // hide flipShadow first, otherwise dark area could be visible 280 | var $flipShadow = this._initShadow(); 281 | 282 | if (this._isVertical()) { 283 | // add class 284 | $flippingElem.addClass('verticalFlipping'); 285 | $flippingBackElem.addClass('verticalFlipping'); 286 | 287 | if (flippingSide === _FIRST_HALF) { 288 | $flipShadow.css('top', '0px'); 289 | } 290 | } else { 291 | $flippingElem.addClass('holizontalFlipping'); 292 | $flippingBackElem.addClass('holizontalFlipping'); 293 | 294 | if (flippingSide === _FIRST_HALF) { 295 | $flipShadow.css('left', '0px'); 296 | } 297 | } 298 | 299 | $elem.append($flipShadow); 300 | 301 | // create flip element for page forward 302 | $flippingElem.addClass('flipping'); 303 | $flippingBackElem.addClass('backflipping'); 304 | 305 | // flip start 306 | $cur = $elem.children(CLASS_CURRENT); 307 | $cur.removeClass(CURRENT); 308 | $cur.addClass('working'); 309 | 310 | $flipShadow.show(); 311 | } 312 | 313 | Flip.prototype.step = function(ratio) { 314 | var $working = $(this.element).children('.working'); 315 | if ($working.length === 0) { 316 | return; 317 | } 318 | 319 | this.currentRatio = ratio; 320 | 321 | var $flipping = this.flippingElement; 322 | var $backFlipping = this.backFilippingElement; 323 | 324 | // move page element 325 | var transformKey = CSSPREFIX + 'Transform'; 326 | $flipping.css(transformKey, this._calculateFlipRotateCSS(ratio)); 327 | $backFlipping.css(transformKey, this._calculateBackFlipRotateCSS(ratio)); 328 | 329 | // control which side is visible and flipShadow div 330 | // basically, it is just y = -1.2*x^2 + 1.2 331 | var formula = function(p) { 332 | var factor = 1.2; 333 | return -1 * factor * (p - 1) * (p - 1) + factor; 334 | } 335 | 336 | var $elem = $(this.element); 337 | var elemHeight = $flipping.height(); 338 | var flipShadowRatio = 0; 339 | 340 | var degree = Math.abs(180 * ratio); 341 | if (degree > 90) { 342 | $flipping.css('zIndex', FLIPPING_BASE_ZINDEX); 343 | $backFlipping.css('zIndex', FLIPPING_BASE_ZINDEX+1); 344 | 345 | if ($backFlipping.css('display') === 'none') { 346 | $backFlipping.css('display', 'block'); 347 | $flipping.css('display', 'none'); 348 | } 349 | 350 | // move flipShadow position (right-to-left or left-to-right) 351 | if (this._isVertical()) { 352 | if (this.flippingSide === _SECOND_HALF) { 353 | $('.flipShadow').css('top', '0'); 354 | } else { 355 | $('.flipShadow').css('top', elemHeight + 'px'); 356 | } 357 | } else { 358 | if (this.flippingSide === _SECOND_HALF) { 359 | $('.flipShadow').css('left', '0'); 360 | } else { 361 | $('.flipShadow').css('left', '50%'); 362 | } 363 | } 364 | 365 | 366 | // set new flipShadow 367 | flipShadowRatio = Math.max(0, 1 - formula((180 - degree) / 90)); 368 | $('.flipShadow').css('opacity', 0.9 * flipShadowRatio); 369 | 370 | } else { 371 | $flipping.css('zIndex', FLIPPING_BASE_ZINDEX+1); 372 | $backFlipping.css('zIndex', FLIPPING_BASE_ZINDEX); 373 | 374 | if ($flipping.css('display') === 'none') { 375 | $backFlipping.css('display', 'none'); 376 | $flipping.css('display', 'block'); 377 | } 378 | 379 | if (this._isVertical()) { 380 | if (this.flippingSide === _SECOND_HALF) { 381 | // vertical flipshadow must calculate height based on the 382 | // flipping object because pager area affect height=50% 383 | $('.flipShadow').css('top', elemHeight + 'px'); 384 | } else { 385 | $('.flipShadow').css('top', '0'); 386 | } 387 | } else { 388 | if (this.flippingSide === _SECOND_HALF) { 389 | $('.flipShadow').css('left', '50%'); 390 | } else { 391 | $('.flipShadow').css('left', '0'); 392 | } 393 | } 394 | 395 | flipShadowRatio = Math.max(0, 1 - formula((degree) / 90)); 396 | $('.flipShadow').css('opacity', 0.9 * flipShadowRatio); 397 | } 398 | } 399 | 400 | Flip.prototype._calculateFlipRotateCSS = function (ratio) 401 | { 402 | if (this._isVertical()) { 403 | return 'rotateX(' + (-1 * 180 * ratio) + 'deg)'; 404 | } else { 405 | return 'rotateY(' + (180 * ratio) + 'deg)'; 406 | } 407 | } 408 | 409 | Flip.prototype._calculateBackFlipRotateCSS = function(ratio) 410 | { 411 | if (this._isVertical()) { 412 | if (this.flippingSide === _FIRST_HALF) { 413 | return 'rotateX(' + (-1 * (180 + (180 * ratio))) + 'deg)'; 414 | } else { 415 | return 'rotateX(' + (180 - (180 * ratio)) + 'deg)'; 416 | } 417 | } else { 418 | if (this.flippingSide === _FIRST_HALF) { 419 | return 'rotateY(' + (180 + (180 * ratio)) + 'deg)'; 420 | } else { 421 | return 'rotateY(' + (-1 * (180 - (180 * ratio))) + 'deg)'; 422 | } 423 | } 424 | } 425 | 426 | 427 | Flip.prototype.actionBK = function(to) { 428 | var endRatio = to; 429 | var startRatio = this.currentRatio; 430 | 431 | // get propert rotateX or rotateY setting 432 | var $flipping = this.flippingElement; 433 | var $backFlipping = this.backFilippingElement; 434 | 435 | // insert this into the 436 | $flipping.addClass("flipAction"); 437 | $backFlipping.addClass("flipBackAction"); 438 | 439 | // insert keyframe 440 | var flipStartRotate = this._calculateFlipRotateCSS(startRatio); 441 | var flipEndRotate = this._calculateFlipRotateCSS(endRatio); 442 | 443 | var keyframe = '@keyframes flip {'; 444 | keyframe += '0% {'+flipStartRotate+';}'; 445 | keyframe += '100% {'+flipEndRotate+';}'; 446 | keyframe += '}'; 447 | 448 | // FIXME: needs to control z-index or visibility 449 | 450 | // add event listener to remove animation after animation finished 451 | 452 | 453 | } 454 | 455 | // This can be done by complete css animation. That should be faster. 456 | Flip.prototype.action = function(to) { 457 | var endRatio = to; 458 | var startRatio = this.currentRatio; 459 | 460 | var _this = this; 461 | var x = 0; 462 | var b = Math.abs(this.currentRatio); 463 | var c = Math.abs(to - this.currentRatio); 464 | var d = c; 465 | 466 | var sign = 1; 467 | if (this.currentRatio < 0) { 468 | sign = -1; 469 | } else if (this.currentRatio === 0) { 470 | if (endRatio < 0) { 471 | sign = -1; 472 | } 473 | } 474 | 475 | var formulaA = function(t) { 476 | // simple version of quadratic easeOut 477 | t = Math.abs(t) / d; 478 | return -1 * c * t * (t - 2) + b; 479 | } 480 | 481 | var formula = function(t) { 482 | // simple version of cubic easeOut 483 | t = Math.abs(t) / d; 484 | t = t - 1; 485 | return c * (t * t * t + 1) + b; 486 | } 487 | 488 | if (to === 0) { 489 | // replace start and end so that same logic can be used 490 | b = 0; 491 | } 492 | 493 | var loop = function() { 494 | // to next step 495 | x = Math.min(1, x + 0.05); 496 | var y = Math.min(1.0, formula(x)); 497 | if (endRatio === 0) { 498 | // set next ratio to the reverse direction 499 | y = Math.abs(startRatio) - y; 500 | } 501 | 502 | // next ratio 503 | if (sign < 0) { 504 | y = -1 * y; 505 | } 506 | 507 | _this.step(y); 508 | 509 | // call until ratio is 1 510 | if (x + 0.05 < d) { 511 | setTimeout(loop, 20); 512 | } else { 513 | // to omit last step 514 | _this.currentRatio = endRatio; 515 | 516 | // end callback 517 | _this.cleanup(); 518 | } 519 | } 520 | 521 | this.isInAnimation = true; 522 | loop(); 523 | } 524 | 525 | Flip.prototype.cleanup = function() { 526 | 527 | var $elem = $(this.element); 528 | var $current = $elem.children('.working'); 529 | 530 | // clear flip elem 531 | $('.flipShadow').replaceWith(''); 532 | $('.firstHalf').replaceWith(''); 533 | $('.secondHalf').replaceWith(''); 534 | 535 | var $nextCurrent = $current; 536 | if (this.options.forwardDir === DIR_LTOR && 537 | this.flippingSide === _FIRST_HALF || 538 | this.options.forwardDir === DIR_RTOL && 539 | this.flippingSide === _SECOND_HALF || 540 | this.options.forwardDir === DIR_TTOB && 541 | this.flippingSide === _FIRST_HALF || 542 | this.options.forwardDir === DIR_BTOT && 543 | this.flippingSide === _SECOND_HALF) { 544 | if (Math.abs(this.currentRatio) === 1) { 545 | $nextCurrent = this.getNextContent($current); 546 | } 547 | } else { 548 | if (Math.abs(this.currentRatio) === 1) { 549 | $nextCurrent = this.getPrevContent($current); 550 | } 551 | } 552 | 553 | // to avoid flushing screen, add .current class first 554 | // then remove .working class 555 | $nextCurrent.addClass(CURRENT); 556 | $current.removeClass('working'); 557 | 558 | // 559 | if (typeof(this.options.didEndFlip) === 'function') { 560 | this.options.didEndFlip(); 561 | } 562 | 563 | this.currentRatio = null; 564 | this.flippingSide = null; 565 | this.isInAnimation = false; 566 | } 567 | 568 | Flip.prototype.getNextContent = function($current) { 569 | 570 | var $nextCurrent = $current.next('.flipContent'); 571 | if ($nextCurrent.length === 0 && this.options.loop) { 572 | var $elem = $(this.element); 573 | $nextCurrent = $elem.children('.flipContent').first(); 574 | } 575 | 576 | return ($nextCurrent.length === 0) ? $() : $nextCurrent; 577 | } 578 | 579 | Flip.prototype.getPrevContent = function($current) { 580 | var $prevCurrent = $current.prev('.flipContent'); 581 | if ($prevCurrent.length === 0 && this.options.loop) { 582 | var $elem = $(this.element); 583 | $prevCurrent = $elem.children('.flipContent').last(); 584 | } 585 | 586 | return ($prevCurrent.length === 0) ? $() : $prevCurrent; 587 | } 588 | 589 | Flip.prototype.shouldTransitionContinue = function(ratio, context) { 590 | var td1 = 1, td2 = 1; 591 | var d1 = 0, d2 = 0; 592 | 593 | if (context.tList.length >= 2) { 594 | td1 = context.tList[context.tList.length - 1]; 595 | td2 = context.tList[context.tList.length - 2]; 596 | 597 | d1 = context.dList[context.dList.length - 1]; 598 | d2 = context.dList[context.dList.length - 2]; 599 | } else if (context.tList.length === 1) { 600 | td1 = context.tList[context.tList.length - 1]; 601 | 602 | d1 = context.dList[context.dList.length - 1]; 603 | } 604 | 605 | var now = new Date().getTime(); 606 | var lastTDiff = (now - context.lastInfo.time) / 100; 607 | var factor = (lastTDiff < 1) ? 1 : 1 / (10 * lastTDiff * lastTDiff); 608 | 609 | var origRatio = ratio; 610 | ratio = ratio + factor * ((d1 / td1) + (d2 / td2)); 611 | //console.log("LastTDiff "+lastTDiff+" Factor: " 612 | //+factor+" Orig Ratio: "+origRatio+" Final Ratio: "+ratio); 613 | 614 | // adjust value 615 | if (this.flippingSide === _FIRST_HALF) 616 | { 617 | ratio = Math.max(0, Math.min(1, ratio)); 618 | } 619 | else 620 | { 621 | ratio = Math.max(-1, Math.min(0, ratio)); 622 | } 623 | 624 | return (Math.abs(ratio) > this.THREASHOLD_RATIO); 625 | } 626 | 627 | function Slide(element, options) { 628 | this.element = element; 629 | this.options = options; 630 | 631 | this.slideSide = null; 632 | this.currentRatio = null; 633 | 634 | this.START_OFFSET = 3; 635 | this.MAX_DISTANCE = 400; // must be initialized at this.init() 636 | 637 | this.isInAnimation = false; 638 | } 639 | 640 | Slide.prototype.init = function(slideSide) { 641 | // this.currentRatio must be null 642 | if (this.currentRatio !== null) { 643 | return; 644 | } 645 | 646 | if (this.options.forwardDir === DIR_RTOL || 647 | this.options.forwardDir === DIR_LTOR) { 648 | this.MAX_DISTANCE = $(this.element).width(); 649 | } else { 650 | this.MAX_DISTANCE = $(this.element).height(); 651 | } 652 | 653 | this.THREASHOLD_RATIO = Math.min(0.15, 100 / this.MAX_DISTANCE); 654 | 655 | // init currentDiff value 656 | this.currentRatio = 0; 657 | this.slideSide = slideSide; 658 | 659 | var $elem = $(this.element); 660 | 661 | var $cur = $elem.children(CLASS_CURRENT); 662 | var $nextContent = null; 663 | var $flippingElem, $flippingBackElem; 664 | var isNext = false; 665 | 666 | if (this.options.forwardDir === DIR_LTOR && slideSide === _FIRST_HALF || 667 | this.options.forwardDir === DIR_RTOL && slideSide === _SECOND_HALF || 668 | this.options.forwardDir === DIR_TTOB && slideSide === _FIRST_HALF || 669 | this.options.forwardDir === DIR_BTOT && slideSide === _SECOND_HALF) { 670 | $nextContent = this.getNextContent($cur); 671 | isNext = true; 672 | } else { 673 | $nextContent = this.getPrevContent($cur); 674 | } 675 | 676 | if ($nextContent === null) { 677 | $nextContent = $(); 678 | } 679 | 680 | // create copy 681 | var $slidingBg = $('.slidingBg'); 682 | if ($slidingBg.length === 0) { 683 | $slidingBg = $('
'); 684 | } else { 685 | $slidingBg.empty(); 686 | } 687 | 688 | $slidingBg.hide(); 689 | 690 | if (isNext) { 691 | $elem.append($slidingBg.append($nextContent.clone())); 692 | } else { 693 | $elem.append($slidingBg.append($cur.clone())); 694 | } 695 | 696 | $slidingBg.css('width', $cur.width() + 'px'); 697 | $slidingBg.css('height', $cur.height() + 'px'); 698 | $slidingBg.children(':first').css('display', 'block'); 699 | 700 | var $sliding = $('.sliding'); 701 | if ($sliding.length === 0) { 702 | $sliding = $('
'); 703 | } else { 704 | $sliding.empty(); 705 | } 706 | 707 | if (isNext) { 708 | $elem.append($sliding.append($cur.clone())); 709 | } else { 710 | $elem.append($sliding.append($nextContent.clone())); 711 | } 712 | 713 | $sliding.css('width', $cur.width() + 'px') 714 | .css('height', $cur.height() + 'px') 715 | .css('zIndex', FLIPPING_BASE_ZINDEX); 716 | 717 | if (!isNext) { 718 | switch (this.options.forwardDir) { 719 | case DIR_RTOL: 720 | $sliding.css('left', -1 * this.MAX_DISTANCE + 'px'); 721 | break; 722 | 723 | case DIR_LTOR: 724 | $sliding.css('left', this.MAX_DISTANCE + 'px'); 725 | break; 726 | 727 | case DIR_TTOB: 728 | $sliding.css('top', this.MAX_DISTANCE + 'px'); 729 | break; 730 | 731 | case DIR_BTOT: 732 | $sliding.css('top', -1 * this.MAX_DISTANCE + 'px'); 733 | break; 734 | } 735 | } 736 | 737 | $sliding.children(':first').css('display', 'block'); 738 | 739 | 740 | // slide start 741 | $cur = $elem.children(CLASS_CURRENT); 742 | $cur.removeClass(CURRENT); 743 | $cur.addClass('working'); 744 | 745 | $slidingBg.show(); 746 | 747 | //$slideShadow.show(); 748 | } 749 | 750 | Slide.prototype.step = function(ratio) { 751 | this.currentRatio = ratio; 752 | var distance = this.MAX_DISTANCE * ratio; 753 | 754 | var $elem = $(this.element); 755 | var $sliding = $elem.children('.sliding'); 756 | 757 | var height = $elem.height(); 758 | 759 | // slide 760 | switch (this.options.forwardDir) { 761 | case DIR_LTOR: 762 | if (this.slideSide === _FIRST_HALF) { 763 | $sliding.css('left', distance + 'px'); 764 | } else { 765 | $sliding.css('left', this.MAX_DISTANCE + distance + 'px'); 766 | } 767 | break; 768 | 769 | case DIR_RTOL: 770 | if (this.slideSide === _FIRST_HALF) { 771 | $sliding.css('left', -1 * this.MAX_DISTANCE + distance + 'px'); 772 | } else { 773 | $sliding.css('left', distance + 'px'); 774 | } 775 | break; 776 | 777 | case DIR_TTOB: 778 | if (this.slideSide === _FIRST_HALF) { 779 | $sliding.css('top', distance + 'px'); 780 | } else { 781 | $sliding.css('top', this.MAX_DISTANCE + distance + 'px'); 782 | } 783 | break; 784 | 785 | case DIR_BTOT: 786 | if (this.slideSide === _FIRST_HALF) { 787 | $sliding.css('top', -1 * this.MAX_DISTANCE + distance + 'px'); 788 | } else { 789 | $sliding.css('top', distance + 'px'); 790 | } 791 | break; 792 | } 793 | } 794 | 795 | Slide.prototype.action = function(to) { 796 | var endRatio = to; 797 | 798 | var _this = this; 799 | var step = 0.09; 800 | 801 | var loop = function() { 802 | var delta = (to - _this.currentRatio < 0) ? -1 : 1; 803 | 804 | // next ratio 805 | var nextRatio = _this.currentRatio; 806 | if (delta < 0) { 807 | nextRatio = Math.max(endRatio, _this.currentRatio + step * delta); 808 | } else { 809 | nextRatio = Math.min(endRatio, _this.currentRatio + step * delta); 810 | } 811 | 812 | _this.step(nextRatio); 813 | 814 | // call until ratio is 1 815 | if (nextRatio !== endRatio) { 816 | setTimeout(loop, 20); 817 | } else { 818 | // end callback 819 | _this.cleanup(); 820 | } 821 | } 822 | 823 | this.isInAnimation = true; 824 | loop(); 825 | } 826 | 827 | Slide.prototype.cleanup = function() { 828 | var $elem = $(this.element); 829 | var $current = $elem.children('.working'); 830 | 831 | // clear flip elem 832 | $('.sliding').replaceWith(''); 833 | $('.slidingBg').replaceWith(''); 834 | $('.slideShadow').replaceWith(''); 835 | 836 | var slideSide = this.slideSide; 837 | var $nextCurrent = $current; 838 | if (this.options.forwardDir === DIR_LTOR && slideSide === _FIRST_HALF || 839 | this.options.forwardDir === DIR_RTOL && slideSide === _SECOND_HALF || 840 | this.options.forwardDir === DIR_TTOB && slideSide === _FIRST_HALF || 841 | this.options.forwardDir === DIR_BTOT && slideSide === _SECOND_HALF) { 842 | if (Math.abs(this.currentRatio) === 1) { 843 | $nextCurrent = this.getNextContent($current); 844 | } 845 | } else { 846 | if (Math.abs(this.currentRatio) === 1) { 847 | $nextCurrent = this.getPrevContent($current); 848 | } 849 | } 850 | 851 | // to avoid flushing screen, add .current class first 852 | // then remove .working class 853 | $nextCurrent.addClass(CURRENT); 854 | $current.removeClass('working'); 855 | 856 | // 857 | if (typeof(this.options.didEndFlip) === 'function') { 858 | this.options.didEndFlip(); 859 | } 860 | 861 | this.currentRatio = null; 862 | this.slideSide = null; 863 | this.isInAnimation = false; 864 | } 865 | 866 | Slide.prototype.getNextContent = function($current) { 867 | var $nextCurrent = $current.next('.flipContent'); 868 | if ($nextCurrent.length === 0 && this.options.loop) { 869 | var $elem = $(this.element); 870 | $nextCurrent = $elem.children('.flipContent').first(); 871 | } 872 | 873 | return ($nextCurrent.length === 0) ? $() : $nextCurrent; 874 | } 875 | 876 | Slide.prototype.getPrevContent = function($current) { 877 | var $prevCurrent = $current.prev('.flipContent'); 878 | if ($prevCurrent.length === 0 && this.options.loop) { 879 | var $elem = $(this.element); 880 | $prevCurrent = $elem.children('.flipContent').last(); 881 | } 882 | 883 | return ($prevCurrent.length === 0) ? $() : $prevCurrent; 884 | } 885 | 886 | Slide.prototype.shouldTransitionContinue = function(ratio, context) { 887 | 888 | var lastDiff = context.dList[context.dList.length - 1]; 889 | 890 | // static check, TODO: should be more complicated 891 | return (Math.abs(ratio) > this.THREASHOLD_RATIO); 892 | } 893 | 894 | 895 | // The actual plugin constructor 896 | function Plugin(element, options) { 897 | this.element = element; 898 | 899 | // jQuery has an extend method which merges the contents of two or 900 | // more objects, storing the result in the first object. The first object 901 | // is generally empty as we don't want to alter the default options for 902 | // future instances of the plugin 903 | this.options = $.extend({}, defaults, options); 904 | 905 | this._defaults = defaults; 906 | this._name = pluginName; 907 | 908 | this.jqmInit(); 909 | 910 | // 911 | this._clickContext = { 912 | downPt: null, 913 | downTime: null, 914 | flipside: null, 915 | dList: [], 916 | tList: [] 917 | }; 918 | 919 | // init effect class 920 | this.options.didEndFlip = this._getCleanupFunc(); 921 | 922 | if (this.options.effect === 'flip' && this.isFlipSupported()) { 923 | this.effect = new Flip(this.element, this.options); 924 | } else { 925 | this.effect = new Slide(this.element, this.options); 926 | } 927 | 928 | this.init(); 929 | } 930 | 931 | Plugin.prototype.jqmInit = function() { 932 | if ($.mobile === null) { 933 | return; 934 | } 935 | 936 | // check element data- attribute for jqm initialization 937 | var $elem = $(this.element); 938 | 939 | if (typeof $.mobile !== 'undefined' && $.mobile.ns) { 940 | _NS = $.mobile.ns; 941 | } 942 | 943 | for (var i = 0, len = JQM_ATTRS.length; i < len; i++) { 944 | var attr = JQM_ATTRS[i]; 945 | var val = $elem.attr(_PREFIX_DATA + _NS + attr.attr); 946 | if (val) { 947 | if (attr.type === TYPE_BOOL) { 948 | this.options[attr.key] = (val.toLowerCase() === 'true') ? true : false; 949 | } else { 950 | this.options[attr.key] = val; 951 | } 952 | } 953 | } 954 | } 955 | 956 | Plugin.prototype.isFlipSupported = function() { 957 | if (!window.getComputedStyle) { 958 | return false; 959 | } 960 | 961 | // detect 3d tranform support by checking its capability 962 | var transforms = { 963 | 'webkitTransform': '-webkit-transform', 964 | 'MozTransform': '-moz-transform', 965 | 'msTransform': '-ms-transform', 966 | 'transform': 'transform' 967 | }; 968 | 969 | var transformType = null; 970 | var flipSupport = false; 971 | var elem = document.body; 972 | for (var transform in transforms) { 973 | var originalValue = window.getComputedStyle(elem).getPropertyValue(transforms[transform]); 974 | elem.style[transform] = "translate3d(1px,1px,1px)"; 975 | flipSupport = window.getComputedStyle(elem).getPropertyValue(transforms[transform]); 976 | elem.style[transform] = originalValue; 977 | 978 | if (flipSupport) { 979 | transformType = transform; 980 | break; 981 | } 982 | } 983 | 984 | if (!flipSupport) { 985 | return false; 986 | } 987 | 988 | // set CSSPREFIX 989 | switch (transformType) { 990 | case 'webkitTransform': 991 | CSSPREFIX = 'webkit'; 992 | break; 993 | 994 | case 'MozTransform': 995 | CSSPREFIX = 'Moz'; 996 | break; 997 | 998 | case 'msTransform': 999 | CSSPREFIX = 'ms'; 1000 | break; 1001 | } 1002 | 1003 | return flipSupport; 1004 | } 1005 | 1006 | /** 1007 | * @return {Object} mouse object. 1008 | */ 1009 | Plugin.prototype._getMouseMovement = function(curX, curY) { 1010 | if (this._clickContext.downPt === null) { 1011 | return null; 1012 | } 1013 | 1014 | var diffX = curX - this._clickContext.downPt.x; 1015 | var diffY = curY - this._clickContext.downPt.y; 1016 | 1017 | var diff = (this.options.forwardDir === DIR_RTOL || 1018 | this.options.forwardDir === DIR_LTOR) ? diffX : diffY; 1019 | 1020 | if (this._clickContext.flipside === null) { 1021 | if (Math.abs(diff) > this.effect.START_OFFSET) { 1022 | if (diff < 0) { 1023 | this._clickContext.flipside = _SECOND_HALF; 1024 | } else { 1025 | this._clickContext.flipside = _FIRST_HALF; 1026 | } 1027 | 1028 | this.effect.init(this._clickContext.flipside); 1029 | } else { 1030 | return null; 1031 | } 1032 | } 1033 | 1034 | // adjust offset 1035 | var sign = (this._clickContext.flipside === _SECOND_HALF) ? -1 : 1; 1036 | diff = diff - (this.effect.START_OFFSET * sign); 1037 | 1038 | return diff; 1039 | } 1040 | 1041 | Plugin.prototype._getClientMousePos = function(event) { 1042 | if (event.clientX === null && event.originalEvent) { 1043 | return {x: event.originalEvent.clientX, y: event.originalEvent.clientY}; 1044 | } else { 1045 | return {x: event.clientX, y: event.clientY}; 1046 | } 1047 | } 1048 | 1049 | Plugin.prototype.vmousedown = function(event) { 1050 | 1051 | if (this._clickContext.downPt) { 1052 | return false; 1053 | } 1054 | 1055 | if (this.effect.isInAnimation) { 1056 | return false; 1057 | } 1058 | 1059 | var mousePos = this._getClientMousePos(event); 1060 | var now = new Date().getTime(); 1061 | 1062 | this._clickContext.downPt = mousePos; 1063 | this._clickContext.downTime = now; 1064 | this._clickContext.lastInfo = {x: mousePos.x, y: mousePos.y, time: now}; 1065 | this._clickContext.dList = []; 1066 | this._clickContext.tList = []; 1067 | 1068 | return true; 1069 | } 1070 | 1071 | Plugin.prototype._getRatio = function(diff) { 1072 | var ratio = diff / this.effect.MAX_DISTANCE; 1073 | 1074 | if (this._clickContext.flipside === _FIRST_HALF) { 1075 | // ratio should be 0 to 1 1076 | ratio = Math.min(Math.max(0, ratio), 1); 1077 | } else { 1078 | ratio = Math.min(Math.max(-1, ratio), 0); 1079 | } 1080 | 1081 | return ratio; 1082 | } 1083 | 1084 | 1085 | Plugin.prototype._pushMouseInfo = function(mousePos) { 1086 | 1087 | // ------------------------------------------------------------- 1088 | // push diff/accl info 1089 | var now = new Date().getTime(); 1090 | var delta = 0; 1091 | if (this.options.forwardDir === DIR_LTOR || 1092 | this.options.forwardDir === DIR_RTOL) { 1093 | delta = mousePos.x - this._clickContext.lastInfo.x; 1094 | } else { 1095 | delta = mousePos.y - this._clickContext.lastInfo.y; 1096 | } 1097 | 1098 | if (delta === 0) { 1099 | return false; 1100 | } 1101 | 1102 | var tDiff = (now - this._clickContext.lastInfo.time); 1103 | 1104 | //console.log("Delta :"+ delta 1105 | //+" (x,y)=("+this._clickContext.lastInfo.x 1106 | //+","+this._clickContext.lastInfo.y+")" 1107 | //+" (x,y)=("+mousePos.x+","+mousePos.y+")" 1108 | //+" tDelta = "+tDiff 1109 | //); 1110 | 1111 | 1112 | // record last 20 1113 | if (this._clickContext.dList.length > 20) { 1114 | this._clickContext.dList.shift(); 1115 | this._clickContext.tList.shift(); 1116 | } 1117 | 1118 | this._clickContext.dList.push(delta); 1119 | this._clickContext.tList.push(tDiff); 1120 | 1121 | this._clickContext.lastInfo = {x: mousePos.x, y: mousePos.y, time: now}; 1122 | 1123 | return true; 1124 | } 1125 | 1126 | Plugin.prototype.vmousemove = function(event) { 1127 | if (this._clickContext.downPt === null) { 1128 | return false; 1129 | } 1130 | 1131 | if (this.effect.isInAnimation) { 1132 | return false; 1133 | } 1134 | 1135 | var mousePos = this._getClientMousePos(event); 1136 | var diff = this._getMouseMovement(mousePos.x, mousePos.y); 1137 | if (diff === null) { 1138 | return false; 1139 | } 1140 | 1141 | // ------------------------------------------------------------- 1142 | // push diff/accl info 1143 | if (!this._pushMouseInfo(mousePos)) { 1144 | return false; 1145 | } 1146 | 1147 | // get ratio 1148 | var ratio = this._getRatio(diff); 1149 | 1150 | this.effect.step(ratio); 1151 | 1152 | event.preventDefault(); 1153 | return false; 1154 | } 1155 | 1156 | Plugin.prototype.flipPrev = function() { 1157 | if (this._clickContext.flipside !== null) { 1158 | return; 1159 | } 1160 | 1161 | var $elem = $(this.element); 1162 | var $current = $elem.children(CLASS_CURRENT); 1163 | 1164 | var $prevCurrent = this.effect.getPrevContent($current); 1165 | var forwardDir = this.options.forwardDir; 1166 | if ($prevCurrent.length > 0) { 1167 | if (forwardDir === DIR_RTOL || forwardDir === DIR_BTOT) { 1168 | this._clickContext.flipside = _FIRST_HALF; 1169 | this.effect.init(this._clickContext.flipside); 1170 | this.effect.action(1); 1171 | } else { 1172 | this._clickContext.flipside = _SECOND_HALF; 1173 | this.effect.init(this._clickContext.flipside); 1174 | this.effect.action(-1); 1175 | } 1176 | // } else { 1177 | // TODO: implement some animation to indicate the first page 1178 | } 1179 | } 1180 | 1181 | Plugin.prototype.flipNext = function() { 1182 | if (this._clickContext.flipside !== null) { 1183 | return; 1184 | } 1185 | 1186 | var $elem = $(this.element); 1187 | var $current = $elem.children(CLASS_CURRENT); 1188 | 1189 | var $nextCurrent = this.effect.getNextContent($current); 1190 | var forwardDir = this.options.forwardDir; 1191 | if ($nextCurrent.length > 0) { 1192 | if (forwardDir === DIR_RTOL || forwardDir === DIR_BTOT) { 1193 | this._clickContext.flipside = _SECOND_HALF; 1194 | this.effect.init(this._clickContext.flipside); 1195 | this.effect.action(-1); 1196 | } else { 1197 | this._clickContext.flipside = _FIRST_HALF; 1198 | this.effect.init(this._clickContext.flipside); 1199 | this.effect.action(1); 1200 | } 1201 | //} else { 1202 | // TODO: implement some animation to indicate the last page 1203 | } 1204 | } 1205 | 1206 | Plugin.prototype._getCleanupFunc = function($nextCurrent) { 1207 | var _this = this; 1208 | 1209 | var origFunc = this.options.didEndFlip; 1210 | 1211 | return function() { 1212 | var idx = $(_this.element).children(CLASS_CURRENT).index(); 1213 | if (_this.options.showPager) { 1214 | // update pager positio 1215 | _this.setPagerPos(idx); 1216 | } 1217 | 1218 | _this._clickContext.downPt = null; 1219 | _this._clickContext.downTime = null; 1220 | _this._clickContext.flipside = null; 1221 | _this._clickContext.lastInfo = null; 1222 | _this._clickContext.dList = []; 1223 | _this._clickContext.aList = []; 1224 | 1225 | if (typeof(origFunc) === 'function') 1226 | { 1227 | // invoke original callback 1228 | origFunc(idx); 1229 | } 1230 | } 1231 | } 1232 | 1233 | Plugin.prototype.vmouseup = function(event) { 1234 | if (this._clickContext.downPt === null) { 1235 | return true; 1236 | } 1237 | 1238 | if (this.effect.isInAnimation) { 1239 | return true; 1240 | } 1241 | 1242 | var mousePos = this._getClientMousePos(event); 1243 | var $elem = $(this.element); 1244 | 1245 | var diff = this._getMouseMovement(mousePos.x, mousePos.y); 1246 | if (diff === null) { 1247 | this._clickContext.downPt = null; 1248 | this._clickContext.flipside = null; 1249 | this._clickContext.downTime = null; 1250 | this._clickContext.lastInfo = null; 1251 | this._clickContext.dList = []; 1252 | this._clickContext.aList = []; 1253 | 1254 | return true; 1255 | } 1256 | 1257 | var ratio = this._getRatio(diff); 1258 | var sign = (this._clickContext.flipside === _SECOND_HALF) ? -1 : 1; 1259 | 1260 | // push mouse info 1261 | // this._pushMouseInfo(mousePos); 1262 | 1263 | var $current = $elem.children('.working'); 1264 | var $nextCur = null; 1265 | var targetRatio = 0; 1266 | var optionForwardDir = this.options.forwardDir; 1267 | var flipside = this._clickContext.flipside; 1268 | if (optionForwardDir === DIR_LTOR && flipside === _FIRST_HALF || 1269 | optionForwardDir === DIR_RTOL && flipside === _SECOND_HALF || 1270 | optionForwardDir === DIR_TTOB && flipside === _FIRST_HALF || 1271 | optionForwardDir === DIR_BTOT && flipside === _SECOND_HALF) { 1272 | 1273 | $nextCur = this.effect.getNextContent($current); 1274 | if (this.effect.shouldTransitionContinue(ratio, this._clickContext) && 1275 | $nextCur.length > 0) { 1276 | targetRatio = sign; 1277 | } else { 1278 | $nextCur = $current; 1279 | targetRatio = 0; 1280 | } 1281 | } else { 1282 | $nextCur = this.effect.getPrevContent($current); 1283 | if (this.effect.shouldTransitionContinue(ratio, this._clickContext) && 1284 | $nextCur.length > 0) { 1285 | targetRatio = sign; 1286 | } else { 1287 | $nextCur = $current; 1288 | targetRatio = 0; 1289 | } 1290 | } 1291 | 1292 | // run animation 1293 | this.effect.action(targetRatio); 1294 | 1295 | event.preventDefault(); 1296 | 1297 | return true; 1298 | } 1299 | 1300 | Plugin.prototype.initPager = function() { 1301 | var PAGER_HEIGHT = 24; 1302 | 1303 | var $elem = $(this.element); 1304 | 1305 | // shrink height for pager area 1306 | var height = $elem.height(); 1307 | var heightCSS = (height - PAGER_HEIGHT) + 'px'; 1308 | $elem.children('div,p,section,article').each(function(idx, child) { 1309 | $(child).css('height', heightCSS); 1310 | }); 1311 | 1312 | var flipPageCount = $elem.children('.flipContent').length; 1313 | $elem.append($('
')); 1314 | 1315 | var $pager = $elem.children('.pager'); 1316 | for (var i = 0; i < flipPageCount; i++) { 1317 | $pager.append($(' ')); 1318 | } 1319 | 1320 | // set current 1321 | var idx = $elem.children(CLASS_CURRENT).index(); 1322 | var $currentA = $pager.children('.dot'); 1323 | $currentA.eq(idx).addClass("current"); 1324 | } 1325 | 1326 | Plugin.prototype.setPagerPos = function(pageIdx) { 1327 | var $elem = $(this.element); 1328 | $elem.find('.pager span.dot.current').removeClass("current"); 1329 | $elem.find('.pager span.dot').eq(pageIdx).addClass("current"); 1330 | } 1331 | 1332 | Plugin.prototype.pagerTap = function(event) { 1333 | // check position 1334 | var $elem = $(this.element); 1335 | 1336 | var $pager = $elem.children('.pager'); 1337 | var pagerWidth = $pager.width(); 1338 | 1339 | var forwardDir = this.options.forwardDir; 1340 | var mouse = this._getClientMousePos(event); 1341 | if (mouse.x < pagerWidth / 2) { 1342 | // backward 1343 | this.flipPrev(); 1344 | } else { 1345 | // forward 1346 | this.flipNext(); 1347 | } 1348 | } 1349 | 1350 | 1351 | Plugin.prototype._isAccessFromMobileBrowser = function() { 1352 | if( navigator.userAgent.match(/Android/i) || 1353 | navigator.userAgent.match(/webOS/i) || 1354 | navigator.userAgent.match(/iPhone/i) || 1355 | navigator.userAgent.match(/iPod/i) || 1356 | navigator.userAgent.match(/BlackBerry/) 1357 | ){ 1358 | return true; 1359 | } 1360 | 1361 | return false; 1362 | } 1363 | 1364 | Plugin.prototype.init = function() { 1365 | // -------------------------------------------------- 1366 | // setup objects 1367 | var $elem = $(this.element); 1368 | var heightCSS = this.options.height; 1369 | 1370 | if (heightCSS === '') { 1371 | var parentHeight = $elem.parent().height(); 1372 | if (parentHeight === 0) { 1373 | parentHeight = $(document.body).height(); 1374 | } 1375 | 1376 | // calculate available height 1377 | heightCSS = parentHeight - $elem.offset().top + 'px'; 1378 | } 1379 | 1380 | $elem.addClass('flipContainer').css('height', heightCSS); 1381 | $elem.children('div,p,section').each(function(idx, child) { 1382 | $(child).addClass('flipContent').css('height', heightCSS); 1383 | }); 1384 | 1385 | var $cur = $elem.children(CLASS_CURRENT); 1386 | if ($cur.length === 0) { 1387 | $elem.children().eq(0).addClass(CURRENT); 1388 | } 1389 | 1390 | // -------------------------------------------------- 1391 | // add event hook 1392 | var mousedown = 'mousedown', 1393 | mousemove = 'mousemove', 1394 | mouseup = 'mouseup', 1395 | click = 'click'; 1396 | 1397 | if ($.mobile) { 1398 | mousedown = 'vmousedown', 1399 | mousemove = 'vmousemove', 1400 | mouseup = 'vmouseup', 1401 | click = 'vclick'; 1402 | } 1403 | 1404 | var _this = this; 1405 | 1406 | // -------------------------------------------------- 1407 | // Special hook for img elem 1408 | // (to prevent native img drag and drop on desktop browsers) 1409 | if (!this._isAccessFromMobileBrowser()) { 1410 | $elem.find('img').each(function(idx, anchor) { 1411 | $(anchor).bind(mousedown, function(event) { 1412 | event.preventDefault(); 1413 | }); 1414 | }); 1415 | } 1416 | 1417 | 1418 | // -------------------------------------------------- 1419 | // init pager 1420 | if (this.options.showPager) { 1421 | this.initPager(); 1422 | 1423 | // attach event handler 1424 | var $pager = $elem.children('.pager'); 1425 | $pager.bind(mousedown, function(event) { 1426 | event.preventDefault(); 1427 | return false; 1428 | }); 1429 | 1430 | $pager.bind(mouseup, function(event) { 1431 | _this.pagerTap(event); 1432 | event.preventDefault(); 1433 | return false; 1434 | }); 1435 | 1436 | } 1437 | 1438 | $(this.element).bind(mousedown, function(event) { 1439 | return _this.vmousedown(event); 1440 | }); 1441 | 1442 | $(this.element).bind(mousemove, function(event) { 1443 | return _this.vmousemove(event); 1444 | }); 1445 | 1446 | $(this.element).bind(mouseup, function(event) { 1447 | return _this.vmouseup(event); 1448 | }); 1449 | 1450 | // ------------------------------------------------- 1451 | // add keyboard hook 1452 | if (this.options.keyboardNav) { 1453 | $(document) 1454 | .unbind('keydown.flip') 1455 | .bind('keydown.flip', function(e) { 1456 | if (!$(e.target).is('input, textarea, select, button')) { 1457 | // Left arrow 1458 | var forwardDir = _this.options.forwardDir; 1459 | if (e.keyCode === KEY_LEFT) { 1460 | if (forwardDir === DIR_RTOL) { 1461 | _this.flipNext(); 1462 | } else { 1463 | _this.flipPrev(); 1464 | } 1465 | } 1466 | 1467 | // Right arrow 1468 | else if (e.keyCode === KEY_RIGHT) { 1469 | if (forwardDir === DIR_RTOL) { 1470 | _this.flipPrev(); 1471 | } else { 1472 | _this.flipNext(); 1473 | } 1474 | } 1475 | } 1476 | }); 1477 | } 1478 | }; 1479 | 1480 | // A really lightweight plugin wrapper around the constructor, 1481 | // preventing against multiple instantiations 1482 | $.fn[pluginName] = function(options) { 1483 | if (this.length > 1) { 1484 | // force to disable keyboard support 1485 | options.keyboardNav = false; 1486 | } 1487 | 1488 | return this.each(function() { 1489 | if (!$.data(this, 'plugin_' + pluginName)) { 1490 | var instance = new Plugin(this, options); 1491 | $.data(this, 'plugin_' + pluginName, instance); 1492 | } 1493 | }); 1494 | } 1495 | 1496 | // bind 1497 | $(document).bind('pageinit create', function(e) { 1498 | $(':jqmData(role="flip")', e.target).flip(); 1499 | }); 1500 | 1501 | })(jQuery, window, document); 1502 | -------------------------------------------------------------------------------- /jquery.mobile.flip.min.css: -------------------------------------------------------------------------------- 1 | .flipContainer{position:relative;-webkit-perspective:3000px;-moz-perspective:3000px;perspective:3000px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.flipContent{position:absolute;top:0;left:0;height:100%;width:100%;display:none;overflow:hidden}.flipContent.flipCurrent{display:block}.sliding,.slidingBg{position:absolute;overflow:hidden;z-index:1;background-color:inherit}.flipping{background-color:inherit;-webkit-backface-visibility:hidden;-webkit-transform-style:flat;-webkit-transform:rotateY(0deg);-moz-backface-visibility:hidden;-moz-transform-style:preserve3d;-moz-transform:rotateY(0deg);backface-visibility:hidden;transform-style:preserve3d;transform:rotateY(0deg)}.flipping.firstHalf{-webkit-transform-origin:100% 0;-moz-transform-origin:100% 0;transform-origin:100% 0}.flipping.secondHalf{-webkit-transform-origin:0 0;-moz-transform-origin:0 0;transform-origin:0 0}.backflipping{display:none;background-color:inherit;-webkit-backface-visibility:hidden;-webkit-transform-style:flat;-webkit-transform:rotateY(180deg);-moz-backface-visibility:hidden;-moz-transform-style:flat;-moz-transform:rotateY(180deg);backface-visibility:hidden;transform-style:flat;transform:rotateY(180deg)}.holizontalFlipping.firstHalf{-webkit-transform-origin:100% 0;-moz-transform-origin:100% 0;transform-origin:100% 0}.holizontalFlipping.secondHalf{-webkit-transform-origin:0 0;-moz-transform-origin:0 0;transform-origin:0 0}.verticalFlipping.firstHalf{-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;transform-origin:0 100%}.verticalFlipping.secondHalf{-webkit-transform-origin:0 0;-moz-transform-origin:0 0;transform-origin:0 0}.splitHalf{position:absolute;overflow:hidden}.splitEmpty{background-color:#333}.flipContainer .pager{text-align:center;height:24px;line-height:6px;position:absolute;bottom:0;width:100%;cursor:pointer;font-size:6px;overflow:hidden}.flipContainer .pager span.dot{width:6px;display:inline-block}.flipContainer .pager span.dot{text-decoration:none;margin:8px 5px;background-image:url('images/pager6x6.png');background-repeat:no-repeat;background-position:0 -6px}.flipContainer .pager span.dot.current{background-position:0 0} -------------------------------------------------------------------------------- /jquery.mobile.flip.min.js: -------------------------------------------------------------------------------- 1 | (function(d,t,q,x){function l(a,b){this.START_OFFSET=8;this.MAX_DISTANCE=250;this.THREASHOLD_RATIO=.5;this.element=a;this.options=b;this.currentRatio=this.flippingSide=null;this.isInAnimation=!1;this.backFilippingElement=this.flippingElement=null}function n(a,b){this.element=a;this.options=b;this.currentRatio=this.slideSide=null;this.START_OFFSET=3;this.MAX_DISTANCE=400;this.isInAnimation=!1}function h(a,b){this.element=a;this.options=d.extend({},u,b);this._defaults=u;this._name="flip";this.jqmInit(); 2 | this._clickContext={downPt:null,downTime:null,flipside:null,dList:[],tList:[]};this.options.didEndFlip=this._getCleanupFunc();"flip"===this.options.effect&&this.isFlipSupported()?this.effect=new l(this.element,this.options):this.effect=new n(this.element,this.options);this.init()}var u={effect:"flip",forwardDir:"rtol",height:"",keyboardNav:!0,showPager:!1,loop:!1},v="flip-",w=[{key:"effect",attr:"effect",type:0},{key:"forwardDir",attr:"forward-dir",type:0},{key:"height",attr:"height",type:0},{key:"keyboardNav", 3 | attr:"keyboard-nav",type:1},{key:"showPager",attr:"show-pager",type:1},{key:"loop",attr:"loop",type:1}],r="";l.prototype._initShadow=function(){var a=d(this.element).children(".flipCurrent"),b=a.width(),a=a.height(),c=d('
').hide();c.css("position","absolute").css("background-color","#111111").css("opacity","0.9").css("display","none").css("zIndex",1E4);this._isVertical()?c.css("width",b+"px").css("height",a/2+"px").css("top",a/2+"px").css("left","0"):c.css("width",b/ 4 | 2+"px").css("height",a+"px").css("left",b/2+"px").css("top","0");return c};l.prototype._isVertical=function(){return"ttob"===this.options.forwardDir||"btot"===this.options.forwardDir?!0:!1};l.prototype._splitElem=function(a,b){var c=d(this.element);if(null===a||0===a.length){var g=c.width(),e=c.height();if(this.options.showPager)var f=c.children(".pager"),e=e-f.height();a=d('
')}var g=this._isVertical(),e=b+"First",f=b+"Second", 5 | k=c.children(e);0===k.length?k=d('
').css("zIndex",1E4):k.empty();var m=c.children(f);0===m.length?m=d('
').css("zIndex",1E4):m.empty();k.append(a.clone());m.append(a.clone());k.find("a").each(function(a,b){d(b).bind("click",function(a){return!1})});m.find("a").each(function(a,b){d(b).bind("click",function(a){return!1})});var h=a.width(),p=a.height();g?(k.css("width",h+"px").css("height",Math.ceil(p/2)+"px"), 6 | k.children(":first").css("height",p+"px").css("display","block")):(k.css("width",Math.ceil(h/2)+"px").css("height",p+"px"),k.children(":first").css("width",h+"px").css("display","block"));g?(m.css("width",h+"px").css("height",Math.ceil(p/2)+"px").css("top",Math.ceil(p/2)+"px"),m.children(":first").css("height",p+"px").css("display","block").css("top",-1*Math.ceil(p/2)+"px")):(m.css("width",Math.ceil(h/2)+"px").css("height",p+"px").css("left",Math.ceil(h/2)+"px"),m.children(":first").css("width",h+ 7 | "px").css("display","block").css("left",-1*Math.ceil(h/2)+"px"));0===c.children(e).length&&c.append(k);0===c.children(f).length&&c.append(m);return{first:k,second:m}};l.prototype.init=function(a){if(null===this.currentRatio){this.currentRatio=0;this.flippingSide=a;var b=d(this.element),c=b.children(".flipCurrent"),g=null,e=g=null,g="ltor"===this.options.forwardDir&&1===a||"rtol"===this.options.forwardDir&&-1===a||"ttob"===this.options.forwardDir&&1===a||"btot"===this.options.forwardDir&&-1===a?this.getNextContent(c): 8 | this.getPrevContent(c),e=this._splitElem(g,"back"),c=this._splitElem(c,"front");-1===a?(g=c.second,e=e.first):(g=c.first,e=e.second);this.flippingElement=g;this.backFilippingElement=e;g.css("zIndex",10001);e.css("zIndex",1E4);var f=this._initShadow();this._isVertical()?(g.addClass("verticalFlipping"),e.addClass("verticalFlipping"),1===a&&f.css("top","0px")):(g.addClass("holizontalFlipping"),e.addClass("holizontalFlipping"),1===a&&f.css("left","0px"));b.append(f);g.addClass("flipping");e.addClass("backflipping"); 9 | c=b.children(".flipCurrent");c.removeClass("flipCurrent");c.addClass("working");f.show()}};l.prototype.step=function(a){if(0!==d(this.element).children(".working").length){this.currentRatio=a;var b=this.flippingElement,c=this.backFilippingElement,g=r+"Transform";b.css(g,this._calculateFlipRotateCSS(a));c.css(g,this._calculateBackFlipRotateCSS(a));g=function(a){return-1.2*(a-1)*(a-1)+1.2};d(this.element);var e=b.height(),f=0;a=Math.abs(180*a);90this.currentRatio?k=-1:0===this.currentRatio&&0>a&&(k=-1);var h=function(a){a=Math.abs(a)/f;--a;return f*(a*a*a+1)+e};0=== 13 | a&&(e=0);var l=function(){d=Math.min(1,d+.05);var e=Math.min(1,h(d));0===a&&(e=Math.abs(b)-e);0>k&&(e*=-1);c.step(e);d+.05k?1:1/(10*k*k))*(e/c+f/d);a=1===this.flippingSide?Math.max(0,Math.min(1,a)):Math.max(-1,Math.min(0,a));return Math.abs(a)>this.THREASHOLD_RATIO};n.prototype.init=function(a){if(null===this.currentRatio){this.MAX_DISTANCE="rtol"===this.options.forwardDir||"ltor"===this.options.forwardDir?d(this.element).width():d(this.element).height();this.THREASHOLD_RATIO=Math.min(.15,100/this.MAX_DISTANCE);this.currentRatio=0; 17 | this.slideSide=a;var b=d(this.element),c=b.children(".flipCurrent"),g=null,e=!1;"ltor"===this.options.forwardDir&&1===a||"rtol"===this.options.forwardDir&&-1===a||"ttob"===this.options.forwardDir&&1===a||"btot"===this.options.forwardDir&&-1===a?(g=this.getNextContent(c),e=!0):g=this.getPrevContent(c);null===g&&(g=d());a=d(".slidingBg");0===a.length?a=d('
'):a.empty();a.hide();e?b.append(a.append(g.clone())):b.append(a.append(c.clone()));a.css("width",c.width()+"px");a.css("height", 18 | c.height()+"px");a.children(":first").css("display","block");var f=d(".sliding");0===f.length?f=d('
'):f.empty();e?b.append(f.append(c.clone())):b.append(f.append(g.clone()));f.css("width",c.width()+"px").css("height",c.height()+"px").css("zIndex",1E4);if(!e)switch(this.options.forwardDir){case "rtol":f.css("left",-1*this.MAX_DISTANCE+"px");break;case "ltor":f.css("left",this.MAX_DISTANCE+"px");break;case "ttob":f.css("top",this.MAX_DISTANCE+"px");break;case "btot":f.css("top", 19 | -1*this.MAX_DISTANCE+"px")}f.children(":first").css("display","block");c=b.children(".flipCurrent");c.removeClass("flipCurrent");c.addClass("working");a.show()}};n.prototype.step=function(a){this.currentRatio=a;a*=this.MAX_DISTANCE;var b=d(this.element),c=b.children(".sliding");b.height();switch(this.options.forwardDir){case "ltor":1===this.slideSide?c.css("left",a+"px"):c.css("left",this.MAX_DISTANCE+a+"px");break;case "rtol":1===this.slideSide?c.css("left",-1*this.MAX_DISTANCE+a+"px"):c.css("left", 20 | a+"px");break;case "ttob":1===this.slideSide?c.css("top",a+"px"):c.css("top",this.MAX_DISTANCE+a+"px");break;case "btot":1===this.slideSide?c.css("top",-1*this.MAX_DISTANCE+a+"px"):c.css("top",a+"px")}};n.prototype.action=function(a){var b=this,c=function(){var d=0>a-b.currentRatio?-1:1,e=b.currentRatio,e=0>d?Math.max(a,b.currentRatio+.09*d):Math.min(a,b.currentRatio+.09*d);b.step(e);e!==a?setTimeout(c,20):b.cleanup()};this.isInAnimation=!0;c()};n.prototype.cleanup=function(){var a=d(this.element).children(".working"); 21 | d(".sliding").replaceWith("");d(".slidingBg").replaceWith("");d(".slideShadow").replaceWith("");var b=this.slideSide,c=a;"ltor"===this.options.forwardDir&&1===b||"rtol"===this.options.forwardDir&&-1===b||"ttob"===this.options.forwardDir&&1===b||"btot"===this.options.forwardDir&&-1===b?1===Math.abs(this.currentRatio)&&(c=this.getNextContent(a)):1===Math.abs(this.currentRatio)&&(c=this.getPrevContent(a));c.addClass("flipCurrent");a.removeClass("working");"function"===typeof this.options.didEndFlip&& 22 | this.options.didEndFlip();this.slideSide=this.currentRatio=null;this.isInAnimation=!1};n.prototype.getNextContent=function(a){a=a.next(".flipContent");0===a.length&&this.options.loop&&(a=d(this.element).children(".flipContent").first());return 0===a.length?d():a};n.prototype.getPrevContent=function(a){a=a.prev(".flipContent");0===a.length&&this.options.loop&&(a=d(this.element).children(".flipContent").last());return 0===a.length?d():a};n.prototype.shouldTransitionContinue=function(a,b){return Math.abs(a)> 23 | this.THREASHOLD_RATIO};h.prototype.jqmInit=function(){if(null!==d.mobile){var a=d(this.element);"undefined"!==typeof d.mobile&&d.mobile.ns&&(v=d.mobile.ns);for(var b=0,c=w.length;bthis.effect.START_OFFSET)this._clickContext.flipside=0>c?-1:1,this.effect.init(this._clickContext.flipside);else return null;return c-=this.effect.START_OFFSET*(-1===this._clickContext.flipside?-1:1)};h.prototype._getClientMousePos=function(a){return null===a.clientX&&a.originalEvent?{x:a.originalEvent.clientX,y:a.originalEvent.clientY}:{x:a.clientX,y:a.clientY}};h.prototype.vmousedown=function(a){if(this._clickContext.downPt|| 26 | this.effect.isInAnimation)return!1;a=this._getClientMousePos(a);var b=(new Date).getTime();this._clickContext.downPt=a;this._clickContext.downTime=b;this._clickContext.lastInfo={x:a.x,y:a.y,time:b};this._clickContext.dList=[];this._clickContext.tList=[];return!0};h.prototype._getRatio=function(a){a/=this.effect.MAX_DISTANCE;return a=1===this._clickContext.flipside?Math.min(Math.max(0,a),1):Math.min(Math.max(-1,a),0)};h.prototype._pushMouseInfo=function(a){var b=(new Date).getTime(),c=0,c="ltor"=== 27 | this.options.forwardDir||"rtol"===this.options.forwardDir?a.x-this._clickContext.lastInfo.x:a.y-this._clickContext.lastInfo.y;if(0===c)return!1;var d=b-this._clickContext.lastInfo.time;20'));for(var g=a.children(".pager"),e=0;e '));a=a.children(".flipCurrent").index(); 33 | g.children(".dot").eq(a).addClass("current")};h.prototype.setPagerPos=function(a){var b=d(this.element);b.find(".pager span.dot.current").removeClass("current");b.find(".pager span.dot").eq(a).addClass("current")};h.prototype.pagerTap=function(a){var b=d(this.element).children(".pager").width();this._getClientMousePos(a).x limit) { 338 | // mouseup to end interaction 339 | flip.vmouseup(getDumEvent(nextX, nextY)); 340 | 341 | // start check routine after 700 msec 342 | setTimeout(testEndFlipping, 700); 343 | 344 | return; 345 | } else { 346 | setTimeout(function() {loop(step + 1)}, 100); 347 | } 348 | } 349 | 350 | setTimeout(function() {loop(1)}, 0); 351 | } 352 | 353 | function testEndFlipping() { 354 | // check pager position 355 | var flip = getFlipObject(); 356 | 357 | var $elem = $(flip.element); 358 | // 1st dot (index=0) must be current 359 | strictEqual($elem.find('.pager span.dot').eq(0).hasClass('current'), 360 | true, 'Pager is set to 1st one'); 361 | 362 | // resume Qunit test runner 363 | start(); 364 | } 365 | 366 | 367 | 368 | $(document).ready(function() { 369 | $.mobile.ns = 'test-'; 370 | 371 | test('Initialization library', function() { 372 | ok($().flip, 'flip library is loaded.'); 373 | ok($(':jqmData(role="flip")').flip(), 'flip library is initialized.'); 374 | }); 375 | 376 | test("Flip object initialization", function() { 377 | ok($(":jqmData(role='flip')").flip(), "flip library is initialized."); 378 | 379 | // check internal data 380 | var flipObj = getFlipObject(); 381 | 382 | strictEqual(flipObj.options.effect, 'flip', "Default effect option is 'flip'"); 383 | strictEqual(flipObj.options.forwardDir, 'rtol', "Default forward direction option is 'rtol'"); 384 | strictEqual(flipObj.options.height, '424px', "Height is set to 424px in html"); 385 | strictEqual(flipObj.options.keyboardNav, true, "Default keyboard navigation option is 'true'"); 386 | strictEqual(flipObj.options.showPager, true, "showpager option is set to 'true'"); 387 | strictEqual(flipObj.options.loop, true, "Default loop option is set to 'true'"); 388 | 389 | // element check 390 | strictEqual($flip().children(".flipContent").length, 3, "3 elems defined as flip page"); 391 | }); 392 | 393 | test('Test _splitElem', test_SplitElem); 394 | 395 | test('Test basic flip forward/backward animation', function() { 396 | stop(); // stop testrunner for async test 397 | testFlipForward(); 398 | }); 399 | }); 400 | -------------------------------------------------------------------------------- /tests/test_flip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test page for Flip (internal) Class 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | 33 |

jQuery Mobile Flip Test Suite

34 |

35 |

36 |
    37 |
38 |
39 |
43 |
Page 1
44 |
Page 2
45 |
Page 3
46 |
47 |
48 | 49 | 50 | --------------------------------------------------------------------------------