================================================
FILE: license.txt
================================================
turn.js 3rd release
www.turnjs.com
Copyright (c) 2012, Emmanuel Garcia
All rights reserved.
Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Any redistribution, use, or modification is done solely for personal
benefit and not for any commercial purpose or for monetary gain.
THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ''AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
================================================
FILE: readme.md
================================================

**Get the turn.js 4th release on [turnjs.com](http://www.turnjs.com/)**
### What's new in turn.js 4th release?
- Added option `autoCenter`
- Added option `zoom`
- Added property `animating`
- Added property `zoom`
- Added method `center`
- Added method `destroy`
- Added method `is`
- Added method `zoom`
- Added event `missing`
- Added event `zooming`
- Added class `.even`
- Added class `.fixed`
- Added class `.hard`
- Added class `.odd`
- Added class `.own-size`
- Added class `.sheet`
- Added the ignore attribute
- New turn.html4.js
- New scissors.js
- Changed the class `.turn-page` to `.page`
- Improved the animation frame generator with requestAnimationFrame
- Improved the animation speed for hard pages with CSS3 transitions
- Redesigned the event sequence to listen to only three events
- Fixed issue #79
- Fixed issue #91
- Fixed issue about the event order turning + turned
- Fixed issue about appending pages in wrong locations
Available only on [turnjs.com](http://www.turnjs.com/)
* * *
turn.js 3rd release
=========
### Make a flip book with HTML5
Turn.js is a plugin for jQuery that adds a beautiful transition similar to real pages in a book or magazine. It works in all modern browsers including touch devices.
### What's new?
- New `addPage` for creating pages dynamically.
- New `display` for single and double pages.
- Gradients for non-webkit browsers.
#### Usage
**CSS code:**
```css
#magazine{
width: 800px;
height: 400px;
}
#magazine .turn-page{
background-color:#ccc;
}
```
**HTML code:**
```html
Page 1
Page 2
Page 3
```
**JavaScript code:**
```javascript
$('#magazine').turn({gradients: true, acceleration: true});
```
#### Requirements
jQuery 1.7 or later
#### Browser support
* Chrome 12, Safari 5, Firefox 10, IE 9
#### License
Released under a non-commercial BSD license
[Full documentation](https://github.com/blasten/turn.js/wiki/Reference)
* * *
[turnjs.com](http://www.turnjs.com/)
================================================
FILE: turn.js
================================================
/**
* turn.js 3rd release
* www.turnjs.com
*
* Copyright (C) 2012, Emmanuel Garcia.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Any redistribution, use, or modification is done solely for personal
* benefit and not for any commercial purpose or for monetary gain.
*
**/
(function($) {
'use strict';
var has3d,
vendor ='',
PI = Math.PI,
A90 = PI/2,
isTouch = 'ontouchstart' in window,
events = (isTouch) ? {start: 'touchstart', move: 'touchmove', end: 'touchend'}
: {start: 'mousedown', move: 'mousemove', end: 'mouseup'},
// Contansts used for each corner
// tl * tr
// * *
// bl * br
corners = {
backward: ['bl', 'tl'],
forward: ['br', 'tr'],
all: ['tl', 'bl', 'tr', 'br']
},
displays = ['single', 'double'],
// Default options
turnOptions = {
// First page
page: 1,
// Enables gradients
gradients: true,
// Duration of transition in milliseconds
duration: 600,
// Enables hardware acceleration
acceleration: true,
// Display
display: 'double',
// Events
when: null
},
flipOptions = {
// Back page
folding: null,
// Corners
// backward: Activates both tl and bl corners
// forward: Activates both tr and br corners
// all: Activates all the corners
corners: 'forward',
// Size of the active zone of each corner
cornerSize: 100,
// Enables gradients
gradients: true,
// Duration of transition in milliseconds
duration: 600,
// Enables hardware acceleration
acceleration: true
},
// Number of pages in the DOM, minimum value: 6
pagesInDOM = 6,
pagePosition = {0: {top: 0, left: 0, right: 'auto', bottom: 'auto'},
1: {top: 0, right: 0, left: 'auto', bottom: 'auto'}},
// Gets basic attributes for a layer
divAtt = function(top, left, zIndex, overf) {
return {'css': {
position: 'absolute',
top: top,
left: left,
'overflow': overf || 'hidden',
'z-index': zIndex || 'auto'
}
};
},
// Gets a 2D point from a bezier curve of four points
bezier = function(p1, p2, p3, p4, t) {
var mum1 = 1 - t,
mum13 = mum1 * mum1 * mum1,
mu3 = t * t * t;
return point2D(Math.round(mum13*p1.x + 3*t*mum1*mum1*p2.x + 3*t*t*mum1*p3.x + mu3*p4.x),
Math.round(mum13*p1.y + 3*t*mum1*mum1*p2.y + 3*t*t*mum1*p3.y + mu3*p4.y));
},
// Converts an angle from degrees to radians
rad = function(degrees) {
return degrees/180*PI;
},
// Converts an angle from radians to degrees
deg = function(radians) {
return radians/PI*180;
},
// Gets a 2D point
point2D = function(x, y) {
return {x: x, y: y};
},
// Returns the traslate value
translate = function(x, y, use3d) {
return (has3d && use3d) ? ' translate3d(' + x + 'px,' + y + 'px, 0px) ' : ' translate(' + x + 'px, ' + y + 'px) ';
},
// Returns the rotation value
rotate = function(degrees) {
return ' rotate(' + degrees + 'deg) ';
},
// Checks if a property belongs to an object
has = function(property, object) {
return Object.prototype.hasOwnProperty.call(object, property);
},
// Gets the CSS3 vendor prefix
getPrefix = function() {
var vendorPrefixes = ['Moz','Webkit','Khtml','O','ms'],
len = vendorPrefixes.length,
vendor = '';
while (len--)
if ((vendorPrefixes[len] + 'Transform') in document.body.style)
vendor='-'+vendorPrefixes[len].toLowerCase()+'-';
return vendor;
},
// Adds gradients
gradient = function(obj, p0, p1, colors, numColors) {
var j, cols = [];
if (vendor=='-webkit-') {
for (j = 0; jlastPage)
throw new Error ('It is impossible to add the page "'+page+'", the maximum value is: "'+lastPage+'"');
} else {
page = lastPage;
incPages = true;
}
if (page>=1 && page<=lastPage) {
// Stop animations
if (data.done) this.turn('stop');
// Move pages if it's necessary
if (page in data.pageObjs)
turnMethods._movePages.call(this, page, 1);
// Update number of pages
if (incPages)
data.totalPages = lastPage;
// Add element
data.pageObjs[page] = $(element).addClass('turn-page p' + page);
// Add page
turnMethods._addPage.call(this, page);
// Update view
if (data.done)
this.turn('update');
turnMethods._removeFromDOM.call(this);
}
return this;
},
// Adds a page from internal data
_addPage: function(page) {
var data = this.data(),
element = data.pageObjs[page];
if (element)
if (turnMethods._necessPage.call(this, page)) {
if (!data.pageWrap[page]) {
var pageWidth = (data.display=='double') ? this.width()/2 : this.width(),
pageHeight = this.height();
element.css({width:pageWidth, height:pageHeight});
// Place
data.pagePlace[page] = page;
// Wrapper
data.pageWrap[page] = $('', {'class': 'turn-page-wrapper',
page: page,
css: {position: 'absolute',
overflow: 'hidden',
width: pageWidth,
height: pageHeight}}).
css(pagePosition[(data.display=='double') ? page%2 : 0]);
// Append to this
this.append(data.pageWrap[page]);
// Move data.pageObjs[page] (element) to wrapper
data.pageWrap[page].prepend(data.pageObjs[page]);
}
// If the page is in the current view, create the flip effect
if (!page || turnMethods._setPageLoc.call(this, page)==1)
turnMethods._makeFlip.call(this, page);
} else {
// Place
data.pagePlace[page] = 0;
// Remove element from the DOM
if (data.pageObjs[page])
data.pageObjs[page].remove();
}
},
// Checks if a page is in memory
hasPage: function(page) {
return page in this.data().pageObjs;
},
// Prepares the flip effect for a page
_makeFlip: function(page) {
var data = this.data();
if (!data.pages[page] && data.pagePlace[page]==page) {
var single = data.display=='single',
even = page%2;
data.pages[page] = data.pageObjs[page].
css({width: (single) ? this.width() : this.width()/2, height: this.height()}).
flip({page: page,
next: (single && page === data.totalPages) ? page -1 : ((even || single) ? page+1 : page-1),
turn: this,
duration: data.opts.duration,
acceleration : data.opts.acceleration,
corners: (single) ? 'all' : ((even) ? 'forward' : 'backward'),
backGradient: data.opts.gradients,
frontGradient: data.opts.gradients
}).
flip('disable', data.disabled).
bind('pressed', turnMethods._pressed).
bind('released', turnMethods._released).
bind('start', turnMethods._start).
bind('end', turnMethods._end).
bind('flip', turnMethods._flip);
}
return data.pages[page];
},
// Makes pages within a range
_makeRange: function() {
var page,
data = this.data(),
range = this.turn('range');
for (page = range[0]; page<=range[1]; page++)
turnMethods._addPage.call(this, page);
},
// Returns a range of `pagesInDOM` pages that should be in the DOM
// Example:
// - page of the current view, return true
// * page is in the range, return true
// 0 page is not in the range, return false
//
// 1 2-3 4-5 6-7 8-9 10-11 12-13
// ** ** -- ** **
range: function(page) {
var remainingPages, left, right,
data = this.data();
page = page || data.tpage || data.page;
var view = turnMethods._view.call(this, page);
if (page<1 || page>data.totalPages)
throw new Error ('"'+page+'" is not a page for range');
view[1] = view[1] || view[0];
if (view[0]>=1 && view[1]<=data.totalPages) {
remainingPages = Math.floor((pagesInDOM-2)/2);
if (data.totalPages-view[1] > view[0]) {
left = Math.min(view[0]-1, remainingPages);
right = 2*remainingPages-left;
} else {
right = Math.min(data.totalPages-view[1], remainingPages);
left = 2*remainingPages-right;
}
} else {
left = pagesInDOM-1;
right = pagesInDOM-1;
}
return [Math.max(1, view[0]-left), Math.min(data.totalPages, view[1]+right)];
},
// Detects if a page is within the range of `pagesInDOM` from the current view
_necessPage: function(page) {
if (page===0)
return true;
var range = this.turn('range');
return page>=range[0] && page<=range[1];
},
// Releases memory by removing pages from the DOM
_removeFromDOM: function() {
var page, data = this.data();
for (page in data.pageWrap)
if (has(page, data.pageWrap) && !turnMethods._necessPage.call(this, page))
turnMethods._removePageFromDOM.call(this, page);
},
// Removes a page from DOM and its internal references
_removePageFromDOM: function(page) {
var data = this.data();
if (data.pages[page]) {
var dd = data.pages[page].data();
if (dd.f && dd.f.fwrapper)
dd.f.fwrapper.remove();
data.pages[page].remove();
delete data.pages[page];
}
if (data.pageObjs[page])
data.pageObjs[page].remove();
if (data.pageWrap[page]) {
data.pageWrap[page].remove();
delete data.pageWrap[page];
}
delete data.pagePlace[page];
},
// Removes a page
removePage: function(page) {
var data = this.data();
if (data.pageObjs[page]) {
// Stop animations
this.turn('stop');
// Remove `page`
turnMethods._removePageFromDOM.call(this, page);
delete data.pageObjs[page];
// Move the pages behind `page`
turnMethods._movePages.call(this, page, -1);
// Resize the size of this magazine
data.totalPages = data.totalPages-1;
turnMethods._makeRange.call(this);
// Check the current view
if (data.page>data.totalPages)
this.turn('page', data.totalPages);
}
return this;
},
// Moves pages
_movePages: function(from, change) {
var page,
data = this.data(),
single = data.display=='single',
move = function(page) {
var next = page + change,
odd = next%2;
if (data.pageObjs[page])
data.pageObjs[next] = data.pageObjs[page].removeClass('page' + page).addClass('page' + next);
if (data.pagePlace[page] && data.pageWrap[page]) {
data.pagePlace[next] = next;
data.pageWrap[next] = data.pageWrap[page].css(pagePosition[(single) ? 0 : odd]).attr('page', next);
if (data.pages[page])
data.pages[next] = data.pages[page].flip('options', {
page: next,
next: (single || odd) ? next+1 : next-1,
corners: (single) ? 'all' : ((odd) ? 'forward' : 'backward')
});
if (change) {
delete data.pages[page];
delete data.pagePlace[page];
delete data.pageObjs[page];
delete data.pageWrap[page];
delete data.pageObjs[page];
}
}
};
if (change>0)
for (page=data.totalPages; page>=from; page--) move(page);
else
for (page=from; page<=data.totalPages; page++) move(page);
},
// Sets or Gets the display mode
display: function(display) {
var data = this.data(),
currentDisplay = data.display;
if (display) {
if ($.inArray(display, displays)==-1)
throw new Error ('"'+display + '" is not a value for display');
if (display=='single') {
if (!data.pageObjs[0]) {
this.turn('stop').
css({'overflow': 'hidden'});
data.pageObjs[0] = $('', {'class': 'turn-page p-temporal'}).
css({width: this.width(), height: this.height()}).
appendTo(this);
}
} else {
if (data.pageObjs[0]) {
this.turn('stop').
css({'overflow': ''});
data.pageObjs[0].remove();
delete data.pageObjs[0];
}
}
data.display = display;
if (currentDisplay) {
var size = this.turn('size');
turnMethods._movePages.call(this, 1, 0);
this.turn('size', size.width, size.height).
turn('update');
}
return this;
} else
return currentDisplay;
},
// Detects if the pages are being animated
animating: function() {
return this.data().pageMv.length>0;
},
// Disables and enables the effect
disable: function(bool) {
var page,
data = this.data(),
view = this.turn('view');
data.disabled = bool===undefined || bool===true;
for (page in data.pages)
if (has(page, data.pages))
data.pages[page].flip('disable', bool ? $.inArray(page, view) : false );
return this;
},
// Gets and sets the size
size: function(width, height) {
if (width && height) {
var data = this.data(), pageWidth = (data.display=='double') ? width/2 : width, page;
this.css({width: width, height: height});
if (data.pageObjs[0])
data.pageObjs[0].css({width: pageWidth, height: height});
for (page in data.pageWrap) {
if (!has(page, data.pageWrap)) continue;
data.pageObjs[page].css({width: pageWidth, height: height});
data.pageWrap[page].css({width: pageWidth, height: height});
if (data.pages[page])
data.pages[page].css({width: pageWidth, height: height});
}
this.turn('resize');
return this;
} else {
return {width: this.width(), height: this.height()};
}
},
// Resizes each page
resize: function() {
var page, data = this.data();
if (data.pages[0]) {
data.pageWrap[0].css({left: -this.width()});
data.pages[0].flip('resize', true);
}
for (page = 1; page <= data.totalPages; page++)
if (data.pages[page])
data.pages[page].flip('resize', true);
},
// Removes an animation from the cache
_removeMv: function(page) {
var i, data = this.data();
for (i=0; i0) ? view[0] : 0, (view[1]<=data.totalPages) ? view[1] : 0]
: [(view[0]>0 && view[0]<=data.totalPages) ? view[0] : 0];
},
// Stops animations
stop: function(ok) {
var i, opts, data = this.data(), pages = data.pageMv;
data.pageMv = [];
if (data.tpage) {
data.page = data.tpage;
delete data['tpage'];
}
for (i in pages) {
if (!has(i, pages)) continue;
opts = data.pages[pages[i]].data().f.opts;
flipMethods._moveFoldingPage.call(data.pages[pages[i]], null);
data.pages[pages[i]].flip('hideFoldedPage');
data.pagePlace[opts.next] = opts.next;
if (opts.force) {
opts.next = (opts.page%2===0) ? opts.page-1 : opts.page+1;
delete opts['force'];
}
}
this.turn('update');
return this;
},
// Gets and sets the number of pages
pages: function(pages) {
var data = this.data();
if (pages) {
if (pagespages)
this.turn('page', pages);
}
data.totalPages = pages;
return this;
} else
return data.totalPages;
},
// Sets a page without effect
_fitPage: function(page, ok) {
var data = this.data(), newView = this.turn('view', page);
if (data.page!=page) {
this.trigger('turning', [page, newView]);
if ($.inArray(1, newView)!=-1) this.trigger('first');
if ($.inArray(data.totalPages, newView)!=-1) this.trigger('last');
}
if (!data.pageObjs[page])
return;
data.tpage = page;
this.turn('stop', ok);
turnMethods._removeFromDOM.call(this);
turnMethods._makeRange.call(this);
this.trigger('turned', [page, newView]);
},
// Turns to a page
_turnPage: function(page) {
var current, next,
data = this.data(),
view = this.turn('view'),
newView = this.turn('view', page);
if (data.page!=page) {
this.trigger('turning', [page, newView]);
if ($.inArray(1, newView)!=-1) this.trigger('first');
if ($.inArray(data.totalPages, newView)!=-1) this.trigger('last');
}
if (!data.pageObjs[page])
return;
data.tpage = page;
this.turn('stop');
turnMethods._makeRange.call(this);
if (data.display=='single') {
current = view[0];
next = newView[0];
} else if (view[1] && page>view[1]) {
current = view[1];
next = newView[0];
} else if (view[0] && page view[0]) ? 'br' : 'bl');
else
data.pages[current].flip('turnPage');
}
},
// Gets and sets a page
page: function(page) {
page = parseInt(page, 10);
var data = this.data();
if (page>0 && page<=data.totalPages) {
if (!data.done || $.inArray(page, this.turn('view'))!=-1)
turnMethods._fitPage.call(this, page);
else
turnMethods._turnPage.call(this, page);
return this;
} else
return data.page;
},
// Turns to the next view
next: function() {
var data = this.data();
return this.turn('page', turnMethods._view.call(this, data.page).pop() + 1);
},
// Turns to the previous view
previous: function() {
var data = this.data();
return this.turn('page', turnMethods._view.call(this, data.page).shift() - 1);
},
// Adds a motion to the internal list
_addMotionPage: function() {
var opts = $(this).data().f.opts,
turn = opts.turn,
dd = turn.data();
opts.pageMv = opts.page;
turnMethods._addMv.call(turn, opts.pageMv);
dd.pagePlace[opts.next] = opts.page;
turn.turn('update');
},
// This event is called in context of flip
_start: function(e, opts, corner) {
var data = opts.turn.data(),
event = $.Event('start');
e.stopPropagation();
opts.turn.trigger(event, [opts, corner]);
if (event.isDefaultPrevented()) {
e.preventDefault();
return;
}
if (data.display=='single') {
var left = corner.charAt(1)=='l';
if ((opts.page==1 && left) || (opts.page==data.totalPages && !left))
e.preventDefault();
else {
if (left) {
opts.next = (opts.nextopts.page) ? opts.next : opts.page+1;
}
}
turnMethods._addMotionPage.call(this);
},
// This event is called in context of flip
_end: function(e, turned) {
var that = $(this),
data = that.data().f,
opts = data.opts,
turn = opts.turn,
dd = turn.data();
e.stopPropagation();
if (turned || dd.tpage) {
if (dd.tpage==opts.next || dd.tpage==opts.page) {
delete dd['tpage'];
turnMethods._fitPage.call(turn, dd.tpage || opts.next, true);
}
} else {
turnMethods._removeMv.call(turn, opts.pageMv);
turn.turn('update');
}
},
// This event is called in context of flip
_pressed: function() {
var page,
that = $(this),
data = that.data().f,
turn = data.opts.turn,
pages = turn.data().pages;
for (page in pages)
if (page!=data.opts.page)
pages[page].flip('disable', true);
return data.time = new Date().getTime();
},
// This event is called in context of flip
_released: function(e, point) {
var that = $(this),
data = that.data().f;
e.stopPropagation();
if ((new Date().getTime())-data.time<200 || point.x<0 || point.x>$(this).width()) {
e.preventDefault();
data.opts.turn.data().tpage = data.opts.next;
data.opts.turn.turn('update');
$(that).flip('turnPage');
}
},
// This event is called in context of flip
_flip: function() {
var opts = $(this).data().f.opts;
opts.turn.trigger('turn', [opts.next]);
},
// Calculate the z-index value for pages during the animation
calculateZ: function(mv) {
var i, page, nextPage, placePage, dpage,
that = this,
data = this.data(),
view = this.turn('view'),
currentPage = view[0] || view[1],
r = {pageZ: {}, partZ: {}, pageV: {}},
addView = function(page) {
var view = that.turn('view', page);
if (view[0]) r.pageV[view[0]] = true;
if (view[1]) r.pageV[view[1]] = true;
};
for (i = 0; i=width || c.y>=height) return false;
if (c.y=height-csz) c.corner = 'b';
else return false;
if (c.x<=csz) c.corner+= 'l';
else if (c.x>=width-csz) c.corner+= 'r';
else return false;
return ($.inArray(c.corner, allowedCorners)==-1) ? false : c;
},
_c: function(corner, opts) {
opts = opts || 0;
return ({tl: point2D(opts, opts),
tr: point2D(this.width()-opts, opts),
bl: point2D(opts, this.height()-opts),
br: point2D(this.width()-opts, this.height()-opts)})[corner];
},
_c2: function(corner) {
return {tl: point2D(this.width()*2, 0),
tr: point2D(-this.width(), 0),
bl: point2D(this.width()*2, this.height()),
br: point2D(-this.width(), this.height())}[corner];
},
_foldingPage: function(corner) {
var opts = this.data().f.opts;
if (opts.folding) return opts.folding;
else if(opts.turn) {
var data = opts.turn.data();
if (data.display == 'single')
return (data.pageObjs[opts.next]) ? data.pageObjs[0] : null;
else
return data.pageObjs[opts.next];
}
},
_backGradient: function() {
var data = this.data().f,
turn = data.opts.turn,
gradient = data.opts.backGradient &&
(!turn || turn.data().display=='single' || (data.opts.page!=2 && data.opts.page!=turn.data().totalPages-1) );
if (gradient && !data.bshadow)
data.bshadow = $('', divAtt(0, 0, 1)).
css({'position': '', width: this.width(), height: this.height()}).
appendTo(data.parent);
return gradient;
},
resize: function(full) {
var data = this.data().f,
width = this.width(),
height = this.height(),
size = Math.round(Math.sqrt(Math.pow(width, 2)+Math.pow(height, 2)));
if (full) {
data.wrapper.css({width: size, height: size});
data.fwrapper.css({width: size, height: size}).
children(':first-child').
css({width: width, height: height});
data.fpage.css({width: height, height: width});
if (data.opts.frontGradient)
data.ashadow.css({width: height, height: width});
if (flipMethods._backGradient.call(this))
data.bshadow.css({width: width, height: height});
}
if (data.parent.is(':visible')) {
data.fwrapper.css({top: data.parent.offset().top,
left: data.parent.offset().left});
if (data.opts.turn)
data.fparent.css({top: -data.opts.turn.offset().top, left: -data.opts.turn.offset().left});
}
this.flip('z', data.opts['z-index']);
},
// Prepares the page by adding a general wrapper and another objects
_addPageWrapper: function() {
var att,
data = this.data().f,
parent = this.parent();
if (!data.wrapper) {
var left = this.css('left'),
top = this.css('top'),
width = this.width(),
height = this.height(),
size = Math.round(Math.sqrt(Math.pow(width, 2)+Math.pow(height, 2)));
data.parent = parent;
data.fparent = (data.opts.turn) ? data.opts.turn.data().fparent : $('#turn-fwrappers');
if (!data.fparent) {
var fparent = $('', {css: {'pointer-events': 'none'}}).hide();
fparent.data().flips = 0;
if (data.opts.turn) {
fparent.css(divAtt(-data.opts.turn.offset().top, -data.opts.turn.offset().left, 'auto', 'visible').css).
appendTo(data.opts.turn);
data.opts.turn.data().fparent = fparent;
} else {
fparent.css(divAtt(0, 0, 'auto', 'visible').css).
attr('id', 'turn-fwrappers').
appendTo($('body'));
}
data.fparent = fparent;
}
this.css({position: 'absolute', top: 0, left: 0, bottom: 'auto', right: 'auto'});
data.wrapper = $('', divAtt(0, 0, this.css('z-index'))).
appendTo(parent).
prepend(this);
data.fwrapper = $('', divAtt(parent.offset().top, parent.offset().left)).
hide().
appendTo(data.fparent);
data.fpage = $('', {css: {cursor: 'default'}}).
appendTo($('', divAtt(0, 0, 0, 'visible')).
appendTo(data.fwrapper));
if (data.opts.frontGradient)
data.ashadow = $('', divAtt(0, 0, 1)).
appendTo(data.fpage);
// Save data
flipMethods.setData.call(this, data);
// Set size
flipMethods.resize.call(this, true);
}
},
// Takes a 2P point from the screen and applies the transformation
_fold: function(point) {
var that = this,
a = 0,
alpha = 0,
beta,
px,
gradientEndPointA,
gradientEndPointB,
gradientStartV,
gradientSize,
gradientOpacity,
mv = point2D(0, 0),
df = point2D(0, 0),
tr = point2D(0, 0),
width = this.width(),
height = this.height(),
folding = flipMethods._foldingPage.call(this),
tan = Math.tan(alpha),
data = this.data().f,
ac = data.opts.acceleration,
h = data.wrapper.height(),
o = flipMethods._c.call(this, point.corner),
top = point.corner.substr(0, 1) == 't',
left = point.corner.substr(1, 1) == 'l',
compute = function() {
var rel = point2D((o.x) ? o.x - point.x : point.x, (o.y) ? o.y - point.y : point.y),
tan = (Math.atan2(rel.y, rel.x)),
middle;
alpha = A90 - tan;
a = deg(alpha);
middle = point2D((left) ? width - rel.x/2 : point.x + rel.x/2, rel.y/2);
var gamma = alpha - Math.atan2(middle.y, middle.x),
distance = Math.max(0, Math.sin(gamma) * Math.sqrt(Math.pow(middle.x, 2) + Math.pow(middle.y, 2)));
tr = point2D(distance * Math.sin(alpha), distance * Math.cos(alpha));
if (alpha > A90) {
tr.x = tr.x + Math.abs(tr.y * Math.tan(tan));
tr.y = 0;
if (Math.round(tr.x*Math.tan(PI-alpha)) < height) {
point.y = Math.sqrt(Math.pow(height, 2)+2 * middle.x * rel.x);
if (top) point.y = height - point.y;
return compute();
}
}
if (alpha>A90) {
var beta = PI-alpha, dd = h - height/Math.sin(beta);
mv = point2D(Math.round(dd*Math.cos(beta)), Math.round(dd*Math.sin(beta)));
if (left) mv.x = - mv.x;
if (top) mv.y = - mv.y;
}
px = Math.round(tr.y/Math.tan(alpha) + tr.x);
var side = width - px,
sideX = side*Math.cos(alpha*2),
sideY = side*Math.sin(alpha*2);
df = point2D(Math.round( (left ? side -sideX : px+sideX)), Math.round((top) ? sideY : height - sideY));
// GRADIENTS
gradientSize = side*Math.sin(alpha);
var endingPoint = flipMethods._c2.call(that, point.corner),
far = Math.sqrt(Math.pow(endingPoint.x-point.x, 2)+Math.pow(endingPoint.y-point.y, 2));
gradientOpacity = (far100 ? (gradientSize-100)/gradientSize : 0;
gradientEndPointA = point2D(gradientSize*Math.sin(A90-alpha)/height*100, gradientSize*Math.cos(A90-alpha)/width*100);
if (top) gradientEndPointA.y = 100-gradientEndPointA.y;
if (left) gradientEndPointA.x = 100-gradientEndPointA.x;
}
if (flipMethods._backGradient.call(that)) {
gradientEndPointB = point2D(gradientSize*Math.sin(alpha)/width*100, gradientSize*Math.cos(alpha)/height*100);
if (!left) gradientEndPointB.x = 100-gradientEndPointB.x;
if (!top) gradientEndPointB.y = 100-gradientEndPointB.y;
}
//
tr.x = Math.round(tr.x);
tr.y = Math.round(tr.y);
return true;
},
transform = function(tr, c, x, a) {
var f = ['0', 'auto'], mvW = (width-h)*x[0]/100, mvH = (height-h)*x[1]/100,
v = {left: f[c[0]], top: f[c[1]], right: f[c[2]], bottom: f[c[3]]},
aliasingFk = (a!=90 && a!=-90) ? (left ? -1 : 1) : 0;
x = x[0] + '% ' + x[1] + '%';
that.css(v).transform(rotate(a) + translate(tr.x + aliasingFk, tr.y, ac), x);
data.fpage.parent().css(v);
data.wrapper.transform(translate(-tr.x + mvW-aliasingFk, -tr.y + mvH, ac) + rotate(-a), x);
data.fwrapper.transform(translate(-tr.x + mv.x + mvW, -tr.y + mv.y + mvH, ac) + rotate(-a), x);
data.fpage.parent().transform(rotate(a) + translate(tr.x + df.x - mv.x, tr.y + df.y - mv.y, ac), x);
if (data.opts.frontGradient)
gradient(data.ashadow,
point2D(left?100:0, top?100:0),
point2D(gradientEndPointA.x, gradientEndPointA.y),
[[gradientStartV, 'rgba(0,0,0,0)'],
[((1-gradientStartV)*0.8)+gradientStartV, 'rgba(0,0,0,'+(0.2*gradientOpacity)+')'],
[1, 'rgba(255,255,255,'+(0.2*gradientOpacity)+')']],
3,
alpha);
if (flipMethods._backGradient.call(that))
gradient(data.bshadow,
point2D(left?0:100, top?0:100),
point2D(gradientEndPointB.x, gradientEndPointB.y),
[[0.8, 'rgba(0,0,0,0)'],
[1, 'rgba(0,0,0,'+(0.3*gradientOpacity)+')'],
[1, 'rgba(0,0,0,0)']],
3);
};
switch (point.corner) {
case 'tl' :
point.x = Math.max(point.x, 1);
compute();
transform(tr, [1,0,0,1], [100, 0], a);
data.fpage.transform(translate(-height, -width, ac) + rotate(90-a*2) , '100% 100%');
folding.transform(rotate(90) + translate(0, -height, ac), '0% 0%');
break;
case 'tr' :
point.x = Math.min(point.x, width-1);
compute();
transform(point2D(-tr.x, tr.y), [0,0,0,1], [0, 0], -a);
data.fpage.transform(translate(0, -width, ac) + rotate(-90+a*2) , '0% 100%');
folding.transform(rotate(270) + translate(-width, 0, ac), '0% 0%');
break;
case 'bl' :
point.x = Math.max(point.x, 1);
compute();
transform(point2D(tr.x, -tr.y), [1,1,0,0], [100, 100], -a);
data.fpage.transform(translate(-height, 0, ac) + rotate(-90+a*2), '100% 0%');
folding.transform(rotate(270) + translate(-width, 0, ac), '0% 0%');
break;
case 'br' :
point.x = Math.min(point.x, width-1);
compute();
transform(point2D(-tr.x, -tr.y), [0,1,1,0], [0, 100], a);
data.fpage.transform(rotate(90-a*2), '0% 0%');
folding.transform(rotate(90) + translate(0, -height, ac), '0% 0%');
break;
}
data.point = point;
},
_moveFoldingPage: function(bool) {
var data = this.data().f,
folding = flipMethods._foldingPage.call(this);
if (folding) {
if (bool) {
if (!data.fpage.children()[data.ashadow? '1' : '0']) {
flipMethods.setData.call(this, {backParent: folding.parent()});
data.fpage.prepend(folding);
}
} else {
if (data.backParent)
data.backParent.prepend(folding);
}
}
},
_showFoldedPage: function(c, animate) {
var folding = flipMethods._foldingPage.call(this),
dd = this.data(),
data = dd.f;
if (!data.point || data.point.corner!=c.corner) {
var event = $.Event('start');
this.trigger(event, [data.opts, c.corner]);
if (event.isDefaultPrevented())
return false;
}
if (folding) {
if (animate) {
var that = this, point = (data.point && data.point.corner==c.corner) ? data.point : flipMethods._c.call(this, c.corner, 1);
this.animatef({from: [point.x, point.y], to:[c.x, c.y], duration: 500, frame: function(v) {
c.x = Math.round(v[0]);
c.y = Math.round(v[1]);
flipMethods._fold.call(that, c);
}});
} else {
flipMethods._fold.call(this, c);
if (dd.effect && !dd.effect.turning)
this.animatef(false);
}
if (!data.fwrapper.is(':visible')) {
data.fparent.show().data().flips++;
flipMethods._moveFoldingPage.call(this, true);
data.fwrapper.show();
if (data.bshadow)
data.bshadow.show();
}
return true;
}
return false;
},
hide: function() {
var data = this.data().f,
folding = flipMethods._foldingPage.call(this);
if ((--data.fparent.data().flips)===0)
data.fparent.hide();
this.css({left: 0, top: 0, right: 'auto', bottom: 'auto'}).transform('', '0% 100%');
data.wrapper.transform('', '0% 100%');
data.fwrapper.hide();
if (data.bshadow)
data.bshadow.hide();
folding.transform('', '0% 0%');
return this;
},
hideFoldedPage: function(animate) {
var data = this.data().f;
if (!data.point) return;
var that = this,
p1 = data.point,
hide = function() {
data.point = null;
that.flip('hide');
that.trigger('end', [false]);
};
if (animate) {
var p4 = flipMethods._c.call(this, p1.corner),
top = (p1.corner.substr(0,1)=='t'),
delta = (top) ? Math.min(0, p1.y-p4.y)/2 : Math.max(0, p1.y-p4.y)/2,
p2 = point2D(p1.x, p1.y+delta),
p3 = point2D(p4.x, p4.y-delta);
this.animatef({
from: 0,
to: 1,
frame: function(v) {
var np = bezier(p1, p2, p3, p4, v);
p1.x = np.x;
p1.y = np.y;
flipMethods._fold.call(that, p1);
},
complete: hide,
duration: 800,
hiding: true
});
} else {
this.animatef(false);
hide();
}
},
turnPage: function(corner) {
var that = this,
data = this.data().f;
corner = {corner: (data.corner) ? data.corner.corner : corner || flipMethods._cAllowed.call(this)[0]};
var p1 = data.point || flipMethods._c.call(this, corner.corner, (data.opts.turn) ? data.opts.turn.data().opts.elevation : 0),
p4 = flipMethods._c2.call(this, corner.corner);
this.trigger('flip').
animatef({
from: 0,
to: 1,
frame: function(v) {
var np = bezier(p1, p1, p4, p4, v);
corner.x = np.x;
corner.y = np.y;
flipMethods._showFoldedPage.call(that, corner);
},
complete: function() {
that.trigger('end', [true]);
},
duration: data.opts.duration,
turning: true
});
data.corner = null;
},
moving: function() {
return 'effect' in this.data();
},
isTurning: function() {
return this.flip('moving') && this.data().effect.turning;
},
_eventStart: function(e) {
var data = this.data().f;
if (!data.disabled && !this.flip('isTurning')) {
data.corner = flipMethods._cornerActivated.call(this, e);
if (data.corner && flipMethods._foldingPage.call(this, data.corner)) {
flipMethods._moveFoldingPage.call(this, true);
this.trigger('pressed', [data.point]);
return false;
} else
data.corner = null;
}
},
_eventMove: function(e) {
var data = this.data().f;
if (!data.disabled) {
e = (isTouch) ? e.originalEvent.touches : [e];
if (data.corner) {
var pos = data.parent.offset();
data.corner.x = e[0].pageX-pos.left;
data.corner.y = e[0].pageY-pos.top;
flipMethods._showFoldedPage.call(this, data.corner);
} else if (!this.data().effect && this.is(':visible')) { // roll over
var corner = flipMethods._cornerActivated.call(this, e[0]);
if (corner) {
var origin = flipMethods._c.call(this, corner.corner, data.opts.cornerSize/2);
corner.x = origin.x;
corner.y = origin.y;
flipMethods._showFoldedPage.call(this, corner, true);
} else
flipMethods.hideFoldedPage.call(this, true);
}
}
},
_eventEnd: function() {
var data = this.data().f;
if (!data.disabled && data.point) {
var event = $.Event('released');
this.trigger(event, [data.point]);
if (!event.isDefaultPrevented())
flipMethods.hideFoldedPage.call(this, true);
}
data.corner = null;
},
disable: function(disable) {
flipMethods.setData.call(this, {'disabled': disable});
return this;
}
},
cla = function(that, methods, args) {
if (!args[0] || typeof(args[0])=='object')
return methods.init.apply(that, args);
else if(methods[args[0]] && args[0].toString().substr(0, 1)!='_')
return methods[args[0]].apply(that, Array.prototype.slice.call(args, 1));
else
throw args[0] + ' is an invalid value';
};
$.extend($.fn, {
flip: function(req, opts) {
return cla(this, flipMethods, arguments);
},
turn: function(req) {
return cla(this, turnMethods, arguments);
},
transform: function(transform, origin) {
var properties = {};
if (origin)
properties[vendor+'transform-origin'] = origin;
properties[vendor+'transform'] = transform;
return this.css(properties);
},
animatef: function(point) {
var data = this.data();
if (data.effect)
clearInterval(data.effect.handle);
if (point) {
if (!point.to.length) point.to = [point.to];
if (!point.from.length) point.from = [point.from];
if (!point.easing) point.easing = function (x, t, b, c, data) { return c * Math.sqrt(1 - (t=t/data-1)*t) + b; };
var j, diff = [],
len = point.to.length,
that = this,
fps = point.fps || 30,
time = - fps,
f = function() {
var j, v = [];
time = Math.min(point.duration, time + fps);
for (j = 0; j < len; j++)
v.push(point.easing(1, time, point.from[j], diff[j], point.duration));
point.frame((len==1) ? v[0] : v);
if (time==point.duration) {
clearInterval(data.effect.handle);
delete data['effect'];
that.data(data);
if (point.complete)
point.complete();
}
};
for (j = 0; j < len; j++)
diff.push(point.to[j] - point.from[j]);
data.effect = point;
data.effect.handle = setInterval(f, fps);
this.data(data);
f();
} else {
delete data['effect'];
}
}
});
$.isTouch = isTouch;
})(jQuery);