├── 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 |  
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 |
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 |
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