', {
class: 'b-wrap'
});
$underwrapper.appendto($wrapper);
var options = inoptions,
isinit = false,
isbusy = false,
isplaying = false,
ishoveringright = false,
ishoveringleft = false,
templates = {
//book page with no content
empty: '
',
//transparent item used with closed books
blank: '
'
},
css = {}, anim = {},
hovershadowwidth, hoverfullwidth, hovercurlwidth,
pages = [],
diff,
originalpagetotal, startingpagenumber,
// page content vars
pn, p0, p1, p2, p3, p4, pnwrap, p0wrap, p1wrap, p2wrap, p3wrap, p4wrap, wraps,
// control vars
p3drag, p0drag,
wpercent, worig, hpercent, horig,
pwidth, pwidthn, pwidthh, pheight, speedh,
page = function ($contentnode, index) {
var $el = $wrapper.clone();
var $wrap = $el.find('.b-wrap');
$el.addclass('b-page-' + index);
if (!$contentnode.hasclass('b-page-empty')) {
if (index % 2 !== 0) {
$wrap.addclass('b-wrap-right');
} else {
$wrap.addclass('b-wrap-left');
}
}
$contentnode.appendto($wrap);
return {
index: index,
contentnode: $contentnode[0],
pagenode: $el[0]
};
},
init = function () {
target.addclass('booklet');
// store data for api calls
target.data('jbooklet', this);
// save original number of pages
originalpagetotal = target.children().length;
options.currentindex = 0;
if (originalpagetotal > 500) {
options.manual = false;
}
// generate page markup
initpages();
// initialize options
updateoptions();
// update after initialized
updatepages();
isinit = true;
},
destroy = function () {
// destroy all booklet items
destroycontrols();
destroypages();
target.removeclass('booklet').removedata('booklet');
isinit = false;
},
initpages = function () {
var nodes = [];
var children = target.children();
var length = target.children().length;
var newpage;
var i;
pages = [];
// fix for odd number of pages
if ((length % 2) !== 0) {
children.last().after(templates.blank);
}
// set total page count
options.pagetotal = length;
startingpagenumber = 0;
if (!isinit) {
options.currentindex = 0;
if (!isnan(options.startingpage) &&
options.startingpage <= options.pagetotal &&
options.startingpage > 0) {
if ((options.startingpage % 2) !== 0) {
options.startingpage--;
}
options.currentindex = options.startingpage;
}
}
// load pages
for (i = 0; i < length; i++) {
newpage = new page($(children[i]), i);
nodes.push(newpage.pagenode);
pages.push(newpage);
}
target.append(nodes);
},
updatepages = function () {
updatepagestructure();
updatepagecss();
updatemanualcontrols();
},
updatepagestructure = function () {
var currindex = options.currentindex;
// reset all content
target.find('.b-page').removeclass('b-pn b-p0 b-p1 b-p2 b-p3 b-p4').hide();
// add page classes
if (currindex - 2 >= 0) {
target.find('.b-page-' + (currindex - 2)).addclass('b-pn').show();
target.find('.b-page-' + (currindex - 1)).addclass('b-p0').show();
}
target.find('.b-page-' + (currindex)).addclass('b-p1').show();
target.find('.b-page-' + (currindex + 1)).addclass('b-p2').show();
if (currindex + 3 <= options.pagetotal) {
target.find('.b-page-' + (currindex + 2)).addclass('b-p3').show();
target.find('.b-page-' + (currindex + 3)).addclass('b-p4').show();
}
// save structure elems to vars
pn = target.find('.b-pn');
p0 = target.find('.b-p0');
p1 = target.find('.b-p1');
p2 = target.find('.b-p2');
p3 = target.find('.b-p3');
p4 = target.find('.b-p4');
pnwrap = pn.find('.b-wrap');
p0wrap = p0.find('.b-wrap');
p1wrap = p1.find('.b-wrap');
p2wrap = p2.find('.b-wrap');
p3wrap = p3.find('.b-wrap');
p4wrap = p4.find('.b-wrap');
wraps = target.find('.b-wrap');
},
updatepagecss = function () {
wraps.css(css.wrap);
p0wrap.css(css.p0wrap);
p1.css(css.p1);
p2.css(css.p2);
pn.css(css.pn);
p0.css(css.p0);
p3.stop().css(css.p3);
p4.css(css.p4);
target.width(options.width);
},
destroypages = function () {
var bwrap = target.find('.b-wrap');
// remove booklet markup
bwrap.unwrap();
bwrap.children().unwrap();
target.find('.b-counter, .b-page-blank, .b-page-empty').remove();
},
///////////////////////////////////////////////////////////////////////
// option / control functions
///////////////////////////////////////////////////////////////////////
updateoptions = function (newoptions) {
var didupdate = false;
var parent = target.parent();
var opwidth = options.width;
var opheight = options.height;
// update options if newoptions have been passed in
if (newoptions !== null && newoptions !== undefined) {
// remove page structure, revert to original order
destroypages();
destroycontrols();
options = $.extend({}, options, newoptions);
didupdate = true;
initpages();
}
// set width.
if (opwidth && typeof opwidth === 'string') {
if (opwidth.indexof('px') !== -1) {
options.width = opwidth.replace('px', '');
} else if (opwidth.indexof('%') !== -1) {
wpercent = true;
worig = opwidth;
options.width = parsefloat((opwidth.replace('%', '') / 100) * parent.width());
}
}
// set height.
if (opheight && typeof opheight === 'string') {
if (opheight.indexof('px') !== -1) {
options.height = opheight.replace('px', '');
} else if (opheight.indexof('%') !== -1) {
hpercent = true;
horig = opheight;
options.height = parsefloat((opheight.replace('%', '') / 100) * parent.height());
}
}
//target.width(options.width);
//target.height(options.height);
// save page sizes and other vars
pwidth = options.width / 2;
pwidthn = '-' + pwidth + 'px';
pwidthh = pwidth / 2;
pheight = options.height;
speedh = options.speed / 2;
// set total page count
options.pagetotal = target.children('.b-page').length;
// update all css, as sizes may have changed
updatecssandanimations();
if (isinit) {
updatepages();
}
// percentage resizing
$(window).on('resize.booklet', function () {
if ((wpercent || hpercent)) {
updatepercentagesize();
}
});
isplaying = false;
// if options were updated force pages, controls and menu to update
if (didupdate) {
updatepages();
}
},
updatecssandanimations = function () {
// init base css
css = {
wrap: {
left: 0,
width: pwidth - (options.pagepadding * 2),
height: pheight - (options.pagepadding * 2),
padding: options.pagepadding
},
p0wrap: {
right: 0,
left: 'auto'
},
p1: {
left: 0,
width: pwidth,
height: pheight
},
p2: {
left: pwidth,
width: pwidth,
opacity: 1,
height: pheight
},
pn: {
left: 0,
width: pwidth,
height: pheight
},
p0: {
left: 0,
width: 0,
height: pheight
},
p3: {
left: pwidth * 2,
width: 0,
height: pheight,
paddingleft: 0
},
p4: {
left: pwidth,
width: pwidth,
height: pheight
}
};
hovershadowwidth = 10;
hoverfullwidth = options.hoverwidth + hovershadowwidth;
hovercurlwidth = (options.hoverwidth / 2) + hovershadowwidth;
// init animation params
anim = {
hover: {
speed: options.hoverspeed,
size: options.hoverwidth,
p2: { width: pwidth - hovercurlwidth },
p3: { left: options.width - hoverfullwidth, width: hovercurlwidth },
p3closed: { left: pwidth - options.hoverwidth, width: hovercurlwidth },
p3wrap: { left: hovershadowwidth },
p2end: { width: pwidth },
p2closedend: { width: pwidth, left: 0 },
p3end: { left: options.width, width: 0 },
p3closedend: { left: pwidth, width: 0 },
p3wrapend: { left: 10 },
p1: { left: hovercurlwidth, width: pwidth - hovercurlwidth },
p1wrap: { left: '-' + hovercurlwidth + 'px' },
p0: { left: hovercurlwidth, width: hovercurlwidth },
p0wrap: { right: hovershadowwidth },
p1end: { left: 0, width: pwidth },
p1wrapend: { left: 0 },
p0end: { left: 0, width: 0 },
p0wrapend: { right: 0 }
},
// forward
p2: {
width: 0
},
p2closed: {
width: 0,
left: pwidth
},
p4closed: {
left: pwidth
},
p3in: {
left: pwidthh,
width: pwidthh,
paddingleft: options.shadowbtmwidth
},
p3indrag: {
left: pwidth / 4,
width: pwidth * 0.75,
paddingleft: options.shadowbtmwidth
},
p3out: {
left: 0,
width: pwidth,
paddingleft: 0
},
p3wrapin: {
left: options.shadowbtmwidth
},
p3wrapout: {
left: 0
},
// backwards
p1: {
left: pwidth,
width: 0
},
p1wrap: {
left: pwidthn
},
p0: {
left: pwidth,
width: pwidth
},
p0in: {
left: pwidthh,
width: pwidthh
},
p0out: {
left: pwidth,
width: pwidth
},
p0outclosed: {
left: 0,
width: pwidth
},
p2back: {
left: 0
},
p0wrapdrag: {
right: 0
},
p0wrapin: {
right: options.shadowbtmwidth
},
p0wrapout: {
right: 0
}
};
},
updatepercentagesize = function () {
var parent = target.parent();
// recalculate size for percentage values, called with window is resized
if (wpercent) {
options.width = parsefloat((worig.replace('%', '') / 100) * parent.width());
target.width(options.width);
pwidth = options.width / 2;
pwidthn = '-' + pwidth + 'px';
pwidthh = pwidth / 2;
}
if (hpercent) {
options.height = parsefloat((horig.replace('%', '') / 100) * parent.height());
target.height(options.height);
pheight = options.height;
}
updatecssandanimations();
updatepagecss();
},
updatemanualcontrols = function () {
var origx, newx, diff, fullpercent, shadowpercent, shadoww, curlw,
underw, curlleft, p1wrapleft, bpage = target.find('.b-page');
ishoveringright = ishoveringleft = p3drag = p0drag = false;
if ($.ui && options.manual) {
// manual page turning, check if jquery ui is loaded
if (bpage.draggable()) {
bpage.draggable('destroy').removeclass('b-grab b-grabbing');
}
// implement draggable forward
p3.draggable({
axis: 'x',
containment: [
target.offset().left,
0,
p2.offset().left + pwidth - hoverfullwidth,
pheight
],
drag: function (event, ui) {
p3drag = true;
p3.removeclass('b-grab').addclass('b-grabbing');
// calculate positions
origx = ui.originalposition.left;
newx = ui.position.left;
diff = origx - newx;
fullpercent = diff / origx;
shadowpercent = fullpercent < 0.5 ? fullpercent : (1 - fullpercent);
shadoww = (shadowpercent * options.shadowbtmwidth * 2) + hovershadowwidth;
shadoww = diff / origx >= 0.5 ? shadoww -= hovershadowwidth : shadoww;
// set top page curl width
curlw = hovercurlwidth + diff / 2;
curlw = curlw > pwidth ? pwidth : curlw; // constrain max width
// set bottom page width, hide
underw = pwidth - curlw;
// set values
p3.width(curlw);
p3wrap.css({left: shadoww});
p2.width(underw);
},
stop: function () {
endhoveranimation(false);
if (fullpercent > options.hoverthreshold) {
next();
p3.removeclass('b-grab b-grabbing');
} else {
p3drag = false;
p3.removeclass('b-grabbing').addclass('b-grab');
}
}
});
// implement draggable backwards
p0.draggable({
axis: 'x',
//containment: 'parent',
containment: [
target.offset().left + hovercurlwidth,
0,
target.offset().left + options.width,
pheight
],
drag: function (event, ui) {
p0drag = true;
p0.removeclass('b-grab').addclass('b-grabbing');
// calculate positions
origx = ui.originalposition.left;
newx = ui.position.left;
diff = newx - origx;
fullpercent = diff / (options.width - origx);
if (fullpercent > 1) {
fullpercent = 1;
}
shadowpercent = fullpercent < 0.5 ? fullpercent : (1 - fullpercent);
shadoww = (shadowpercent * options.shadowbtmwidth * 2) + hovershadowwidth;
shadoww = diff / origx >= 0.5 ? shadoww -= hovershadowwidth : shadoww;
curlw = fullpercent * (pwidth - hovercurlwidth) + hovercurlwidth + shadoww;
curlleft = curlw - shadoww;
p1wrapleft = -curlleft;
// set values
ui.position.left = curlleft;
p0.css({width: curlw});
p0wrap.css({right: shadoww});
p1.css({left: curlleft, width: pwidth - curlleft});
p1wrap.css({left: p1wrapleft});
},
stop: function () {
endhoveranimation(true);
if (fullpercent > options.hoverthreshold) {
prev();
p0.removeclass('b-grab b-grabbing');
} else {
p0drag = false;
p0
.removeclass('b-grabbing')
.addclass('b-grab');
}
}
});
bpage.off('click.booklet');
if (options.hoverclick) {
target.find('.b-pn, .b-p0').on('click.booklet', prev).css({cursor: 'pointer'});
target.find('.b-p3, .b-p4').on('click.booklet', next).css({cursor: 'pointer'});
}
// mouse tracking for page movement
target.off('mousemove.booklet').on('mousemove.booklet', function (e) {
diff = e.pagex - target.offset().left;
if (diff < anim.hover.size) {
starthoveranimation(false);
} else if (diff > anim.hover.size && diff <= options.width - anim.hover.size) {
endhoveranimation(false);
endhoveranimation(true);
} else if (diff > options.width - anim.hover.size) {
starthoveranimation(true);
}
}).off('mouseleave.booklet').on('mouseleave.booklet', function () {
endhoveranimation(false);
endhoveranimation(true);
});
}
},
destroycontrols = function () {
destroymanualcontrols();
},
destroymanualcontrols = function () {
if ($.ui) {
// remove old draggables
if (target.find('.b-page').draggable()) {
target.find('.b-page').draggable('destroy').removeclass('b-grab b-grabbing');
}
}
// remove mouse tracking for page movement
target.off('.booklet');
},
/* -------------------- pages -------------------- */
addpage = function (index, html) {
// validate inputs
if (index === 'first') {
index = 0;
} else if (index === 'last') {
index = originalpagetotal;
} else if (typeof index === 'number') {
if (index < 0 || index > originalpagetotal) {
return;
}
} else if (index === undefined) {
return;
}
if (html === undefined || html === '') {
return;
}
// remove page structure, revert to original order
destroypages();
destroycontrols();
// add new page
if (index === originalpagetotal) {
//end of book
target.children(':eq(' + (index - 1) + ')').after(html);
} else {
target.children(':eq(' + index + ')').before(html);
}
originalpagetotal = target.children().length;
// recall initialize functions
initpages();
updateoptions();
updatepages();
},
removepage = function (index) {
var removedpage;
// validate inputs
if (index === 'start') {
index = 0;
} else if (index === 'end') {
index = originalpagetotal;
} else if (typeof index === 'number') {
if (index < 0 || index > originalpagetotal) {
return;
}
} else if (index === undefined) {
return;
}
// stop if removing last remaining page
if (target.children('.b-page').length === 2 && target.find('.b-page-blank').length > 0) {
return;
}
// remove page structure, revert to original order
destroypages();
destroycontrols();
if (index >= options.currentindex) {
if (index > 0 && (index % 2) !== 0) {
options.currentindex -= 2;
}
if (options.currentindex < 0) {
options.currentindex = 0;
}
}
// remove page
if (index === originalpagetotal) {
// end of book
removedpage = target.children(':eq(' + (index - 1) + ')').remove();
} else {
removedpage = target.children(':eq(' + index + ')').remove();
}
originalpagetotal = target.children().length;
removedpage = null;
// recall initialize functions
initpages();
updatepages();
updateoptions();
},
/* -------------------- navigation -------------------- */
next = function () {
if (!isbusy) {
if (isplaying && options.currentindex + 2 >= options.pagetotal) {
gotopage(0);
} else {
gotopage(options.currentindex + 2);
}
}
},
prev = function () {
if (!isbusy) {
if (isplaying && options.currentindex - 2 < 0) {
gotopage(options.pagetotal - 2);
} else {
gotopage(options.currentindex - 2);
}
}
},
gotopage = function (newindex) {
var speed;
if (newindex < options.pagetotal && newindex >= 0 && !isbusy) {
// moving forward (increasing number)
if (newindex > options.currentindex) {
isbusy = true;
diff = newindex - options.currentindex;
options.currentindex = newindex;
options.movingforward = true;
// set animation speed, depending if user dragged any distance or not
speed = p3drag === true ? options.speed * (p3.width() / pwidth) : speedh;
startpageanimation(diff, true, speed);
// hide p2 as p3 moves across it
p2.stop().animate(anim.p2, speed, p3drag === true ? options.easeout : options.easein);
// if animating after a manual drag, calculate new speed and animate out
if (p3drag) {
p3.animate(anim.p3out, speed, options.easeout);
p3wrap.animate(anim.p3wrapout, speed, options.easeout, function () {
updateafter();
});
} else {
p3.stop()
.animate(anim.p3in, speed, options.easein)
.animate(anim.p3out, speed, options.easeout);
p3wrap
.animate(anim.p3wrapin, speed, options.easein)
.animate(anim.p3wrapout, speed, options.easeout, function () {
updateafter();
});
}
} else if (newindex < options.currentindex) {
// moving backward (decreasing number)
isbusy = true;
diff = options.currentindex - newindex;
options.currentindex = newindex;
options.movingforward = false;
// set animation speed, depending if user dragged any distance or not
speed = p0drag === true ? options.speed * (p0.width() / pwidth) : speedh;
startpageanimation(diff, false, speed);
if (p0drag) {
// hide p1 as p0 moves across it
p1.animate(anim.p1, speed, options.easeout);
p1wrap.animate(anim.p1wrap, speed, options.easeout);
p0.animate(anim.p0, speed, options.easeout);
p0wrap.animate(anim.p0wrapdrag, speed, options.easeout, function () {
updateafter();
});
} else {
// hide p1 as p0 moves across it
p1.animate(anim.p1, speed * 2, options.easing);
p1wrap.animate(anim.p1wrap, speed * 2, options.easing);
p0
.animate(anim.p0in, speed, options.easein)
.animate(anim.p0out, speed, options.easeout);
p0wrap
.animate(anim.p0wrapin, speed, options.easein)
.animate(anim.p0wrapout, speed, options.easeout, function () {
updateafter();
});
}
}
}
},
starthoveranimation = function (inc) {
if (options.hovers || options.manual) {
if (inc) {
if (!isbusy && !ishoveringright && !ishoveringleft && !p3drag && options.currentindex + 2 <= options.pagetotal - 2) {
p2.stop().animate(anim.hover.p2, anim.hover.speed, options.easing);
p3.addclass('b-grab');
p3.stop().animate(anim.hover.p3, anim.hover.speed, options.easing);
p3wrap.stop().animate(anim.hover.p3wrap, anim.hover.speed, options.easing);
ishoveringright = true;
}
} else {
if (!isbusy && !ishoveringleft && !ishoveringright && !p0drag && options.currentindex - 2 >= 0) {
p1.stop().animate(anim.hover.p1, anim.hover.speed, options.easing);
p0.addclass('b-grab');
p1wrap.stop().animate(anim.hover.p1wrap, anim.hover.speed, options.easing);
p0.stop().animate(anim.hover.p0, anim.hover.speed, options.easing);
p0wrap.stop().animate(anim.hover.p0wrap, anim.hover.speed, options.easing);
ishoveringleft = true;
}
}
}
},
endhoveranimation = function (inc) {
if (options.hovers || options.manual) {
if (inc) {
if (!isbusy && ishoveringright && !p3drag && options.currentindex + 2 <= options.pagetotal - 2) {
p2.stop().animate(anim.hover.p2end, anim.hover.speed, options.easing);
p3.stop().animate(anim.hover.p3end, anim.hover.speed, options.easing);
p3wrap.stop().animate(anim.hover.p3wrapend, anim.hover.speed, options.easing);
ishoveringright = false;
}
} else {
if (!isbusy && ishoveringleft && !p0drag && options.currentindex - 2 >= 0) {
p1.stop().animate(anim.hover.p1end, anim.hover.speed, options.easing);
p1wrap.stop().animate(anim.hover.p1wrapend, anim.hover.speed, options.easing);
p0.stop().animate(anim.hover.p0end, anim.hover.speed, options.easing);
p0wrap.stop().animate(anim.hover.p0wrapend, anim.hover.speed, options.easing);
ishoveringleft = false;
}
}
}
},
startpageanimation = function (diff, inc) {
var currindex = options.currentindex;
// setup content
if (inc && diff > 2) {
// initialize next 2 pages, if jumping forward in the book
target.find('.b-p3, .b-p4').removeclass('b-p3 b-p4').hide();
target.find('.b-page-' + currindex).addclass('b-p3').show().stop().css(css.p3);
target.find('.b-page-' + (currindex + 1)).addclass('b-p4').show().css(css.p4);
target.find('.b-page-' + currindex + ' .b-wrap').show().css(css.wrap);
target.find('.b-page-' + (currindex + 1) + ' .b-wrap').show().css(css.wrap);
p3 = target.find('.b-p3');
p4 = target.find('.b-p4');
p3wrap = p3.find('.b-wrap');
p4wrap = p4.find('.b-wrap');
if (ishoveringright) {
p3.css({ 'left': options.width - 40, 'width': 20, 'padding-left': 10 });
}
} else if (!inc && diff > 2) {
// initialize previous 2 pages, if jumping backwards in the book
target.find('.b-pn, .b-p0').removeclass('b-pn b-p0').hide();
target.find('.b-page-' + currindex).addclass('b-pn').show().css(css.pn);
target.find('.b-page-' + (currindex + 1)).addclass('b-p0').show().css(css.p0);
target.find('.b-page-' + currindex + ' .b-wrap').show().css(css.wrap);
target.find('.b-page-' + (currindex + 1) + ' .b-wrap').show().css(css.wrap);
pn = target.find('.b-pn');
p0 = target.find('.b-p0');
pnwrap = pn.find('.b-wrap');
p0wrap = p0.find('.b-wrap');
p0wrap.css(css.p0wrap);
if (ishoveringleft) {
p0.css({ left: 10, width: 40 });
p0wrap.css({ right: 10 });
}
}
},
updateafter = function () {
updatepages();
isbusy = false;
};
/* -------------------------- api -------------------------- */
return {
init: init,
destroy: destroy,
next: next,
prev: prev,
gotopage: function (index) {
// validate inputs
if (typeof index === 'string') {
if (index === 'first') {
index = 0;
} else if (index === 'last') {
index = options.pagetotal - 2;
} else {
this.gotopage(parseint(index));
}
} else if (typeof index === 'number') {
if (index < 0 || index >= options.pagetotal) {
return;
}
} else if (index === undefined) {
return;
}
// adjust for odd page
if (index % 2 !== 0) {
index -= 1;
}
gotopage(index);
},
add: addpage,
remove: removepage,
option: function (name, value) {
if (typeof name === 'string') {
// if option exists
if (options[name] !== undefined) {
if (value !== undefined) {
// if value is sent in, set the option value and update options
options[name] = value;
updateoptions();
} else {
// if no value sent in, get the current option value
return options[name];
}
} else {
$.error('option "' + name + '" does not exist on jquery.booklet.');
}
} else if (typeof name === 'object') {
// if sending in an object, update options
updateoptions(name);
} else if (name === undefined || !name) {
// return a copy of the options object, to avoid changes
return $.extend({}, options);
}
}
};
}
// define default options
$.fn.booklet.defaults = {
width: 1200, // container width, px
height: 820, // container height, px
speed: 1000, // speed of the transition between pages
startingpage: 0, // index of the first page to be displayed
easing: 'easeinoutquad', // easing method for complete transition
easein: 'easeinquad', // easing method for first half of transition
easeout: 'easeoutquad', // easing method for second half of transition
pagepadding: 10, // padding for each page wrapper
manual: true, // enables manual page turning, requires jquery ui to function
hovers: true, // enables preview page-turn hover animation, shows a small preview of previous or next page on hover
hoverwidth: 50, // default width for page-turn hover preview
hoverspeed: 500, // default speed for page-turn hover preview
hoverthreshold: 0.25, // default percentage used for manual page dragging, sets the percentage amount a drag must be before moving next or prev
hoverclick: true, // enables hovered arreas to be clicked when using manual page turning
shadowbtmwidth: 30 // shadow width for bottom shadow
};
})(this, jquery);