migrated to iScroll5 and a partial rewrite...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2014-04-19 13:37:20 +04:00
parent 48b30ccb5a
commit fc8b17910e
12 changed files with 8938 additions and 1773 deletions

View File

@ -19,7 +19,7 @@
</author> </author>
<!--preference name="orientation" value="landscape" /--> <preference name="orientation" value="landscape" />
<preference name="fullscreen" value="true" /> <preference name="fullscreen" value="true" />

View File

@ -62,7 +62,7 @@
.top-toolbar, .bottom-toolbar { .top-toolbar, .bottom-toolbar {
font-size: 14px; font-size: 14px;
color: silver; color: silver;
box-shadow: 5px 5px 50px 5px #444 inset; /*box-shadow: 5px 5px 50px 5px #444 inset;*/
} }
.top-toolbar .title, .top-toolbar .title,
@ -154,8 +154,8 @@
.light-viewer .bottom-toolbar { .light-viewer .bottom-toolbar {
font-size: 14px; font-size: 14px;
color: silver; color: silver;
background: white; /*background: white;
box-shadow: 5px 5px 50px 20px #eee; box-shadow: 5px 5px 50px 20px #eee;*/
} }
.light-viewer .top-toolbar a, .light-viewer .top-toolbar a,
.light-viewer .bottom-toolbar a { .light-viewer .bottom-toolbar a {
@ -240,8 +240,8 @@
.dark-viewer .bottom-toolbar { .dark-viewer .bottom-toolbar {
font-size: 14px; font-size: 14px;
color: gray; color: gray;
background: black; /*background: black;
box-shadow: none; box-shadow: none;*/
} }
.dark-viewer .top-toolbar a, .dark-viewer .top-toolbar a,

View File

@ -299,7 +299,8 @@ body {
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
background: #555; /*background: #555;*/
background: transparent;
opacity: 0.9; opacity: 0.9;
height: 50px; height: 50px;
@ -319,12 +320,6 @@ body {
bottom: 0px; bottom: 0px;
width: 100%; width: 100%;
} }
.page-view-mode .top-toolbar,
.page-view-mode .bottom-toolbar,
.full-page-view-mode .top-toolbar,
.full-page-view-mode .bottom-toolbar {
display: none;
}
/* title */ /* title */
.top-toolbar .title, .top-toolbar .title,
@ -560,6 +555,7 @@ body {
.page-view-mode .top-toolbar, .page-view-mode .top-toolbar,
.page-view-mode .bottom-toolbar { .page-view-mode .bottom-toolbar {
display: none;
} }
.page-view-mode .page .content { .page-view-mode .page .content {

1565
ext-lib/iscroll-infinite.js Executable file

File diff suppressed because it is too large Load Diff

594
ext-lib/iscroll-lite.js Executable file
View File

@ -0,0 +1,594 @@
/*!
* iScroll Lite base on iScroll v4.1.6 ~ Copyright (c) 2011 Matteo Spinelli, http://cubiq.org
* Released under MIT license, http://cubiq.org/license
*/
(function(){
var m = Math,
mround = function (r) { return r >> 0; },
vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' :
(/firefox/i).test(navigator.userAgent) ? 'Moz' :
'opera' in window ? 'O' : '',
// Browser capabilities
isAndroid = (/android/gi).test(navigator.appVersion),
isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),
isPlaybook = (/playbook/gi).test(navigator.appVersion),
isTouchPad = (/hp-tablet/gi).test(navigator.appVersion),
has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
hasTouch = 'ontouchstart' in window && !isTouchPad,
hasTransform = vendor + 'Transform' in document.documentElement.style,
hasTransitionEnd = isIDevice || isPlaybook,
nextFrame = (function() {
return window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) { return setTimeout(callback, 17); }
})(),
cancelFrame = (function () {
return window.cancelRequestAnimationFrame
|| window.webkitCancelAnimationFrame
|| window.webkitCancelRequestAnimationFrame
|| window.mozCancelRequestAnimationFrame
|| window.oCancelRequestAnimationFrame
|| window.msCancelRequestAnimationFrame
|| clearTimeout
})(),
// Events
RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize',
START_EV = hasTouch ? 'touchstart' : 'mousedown',
MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',
END_EV = hasTouch ? 'touchend' : 'mouseup',
CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup',
// Helpers
trnOpen = 'translate' + (has3d ? '3d(' : '('),
trnClose = has3d ? ',0)' : ')',
// Constructor
iScroll = function (el, options) {
var that = this,
doc = document,
i;
that.wrapper = typeof el == 'object' ? el : doc.getElementById(el);
that.wrapper.style.overflow = 'hidden';
that.scroller = that.wrapper.children[0];
// Default options
that.options = {
hScroll: true,
vScroll: true,
x: 0,
y: 0,
bounce: true,
bounceLock: false,
momentum: true,
lockDirection: true,
useTransform: true,
useTransition: false,
// Events
onRefresh: null,
onBeforeScrollStart: function (e) { e.preventDefault(); },
onScrollStart: null,
onBeforeScrollMove: null,
onScrollMove: null,
onBeforeScrollEnd: null,
onScrollEnd: null,
onTouchEnd: null,
onDestroy: null
};
// User defined options
for (i in options) that.options[i] = options[i];
// Set starting position
that.x = that.options.x;
that.y = that.options.y;
// Normalize options
that.options.useTransform = hasTransform ? that.options.useTransform : false;
that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar;
that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar;
that.options.useTransition = hasTransitionEnd && that.options.useTransition;
// Set some default styles
that.scroller.style[vendor + 'TransitionProperty'] = that.options.useTransform ? '-' + vendor.toLowerCase() + '-transform' : 'top left';
that.scroller.style[vendor + 'TransitionDuration'] = '0';
that.scroller.style[vendor + 'TransformOrigin'] = '0 0';
if (that.options.useTransition) that.scroller.style[vendor + 'TransitionTimingFunction'] = 'cubic-bezier(0.33,0.66,0.66,1)';
if (that.options.useTransform) that.scroller.style[vendor + 'Transform'] = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose;
else that.scroller.style.cssText += ';position:absolute;top:' + that.y + 'px;left:' + that.x + 'px';
that.refresh();
that._bind(RESIZE_EV, window);
that._bind(START_EV);
if (!hasTouch) that._bind('mouseout', that.wrapper);
};
// Prototype
iScroll.prototype = {
enabled: true,
x: 0,
y: 0,
steps: [],
scale: 1,
handleEvent: function (e) {
var that = this;
switch(e.type) {
case START_EV:
if (!hasTouch && e.button !== 0) return;
that._start(e);
break;
case MOVE_EV: that._move(e); break;
case END_EV:
case CANCEL_EV: that._end(e); break;
case RESIZE_EV: that._resize(); break;
case 'mouseout': that._mouseout(e); break;
case 'webkitTransitionEnd': that._transitionEnd(e); break;
}
},
_resize: function () {
this.refresh();
},
_pos: function (x, y) {
x = this.hScroll ? x : 0;
y = this.vScroll ? y : 0;
if (this.options.useTransform) {
this.scroller.style[vendor + 'Transform'] = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + this.scale + ')';
} else {
x = mround(x);
y = mround(y);
this.scroller.style.left = x + 'px';
this.scroller.style.top = y + 'px';
}
this.x = x;
this.y = y;
},
_start: function (e) {
var that = this,
point = hasTouch ? e.touches[0] : e,
matrix, x, y;
if (!that.enabled) return;
if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e);
if (that.options.useTransition) that._transitionTime(0);
that.moved = false;
that.animating = false;
that.zoomed = false;
that.distX = 0;
that.distY = 0;
that.absDistX = 0;
that.absDistY = 0;
that.dirX = 0;
that.dirY = 0;
if (that.options.momentum) {
if (that.options.useTransform) {
// Very lame general purpose alternative to CSSMatrix
matrix = getComputedStyle(that.scroller, null)[vendor + 'Transform'].replace(/[^0-9-.,]/g, '').split(',');
x = matrix[4] * 1;
y = matrix[5] * 1;
} else {
x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1;
y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1;
}
if (x != that.x || y != that.y) {
if (that.options.useTransition) that._unbind('webkitTransitionEnd');
else cancelFrame(that.aniTime);
that.steps = [];
that._pos(x, y);
}
}
that.startX = that.x;
that.startY = that.y;
that.pointX = point.pageX;
that.pointY = point.pageY;
that.startTime = e.timeStamp || Date.now();
if (that.options.onScrollStart) that.options.onScrollStart.call(that, e);
that._bind(MOVE_EV);
that._bind(END_EV);
that._bind(CANCEL_EV);
},
_move: function (e) {
var that = this,
point = hasTouch ? e.touches[0] : e,
deltaX = point.pageX - that.pointX,
deltaY = point.pageY - that.pointY,
newX = that.x + deltaX,
newY = that.y + deltaY,
timestamp = e.timeStamp || Date.now();
if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e);
that.pointX = point.pageX;
that.pointY = point.pageY;
// Slow down if outside of the boundaries
if (newX > 0 || newX < that.maxScrollX) {
newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX;
}
if (newY > 0 || newY < that.maxScrollY) {
newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= 0 || that.maxScrollY >= 0 ? 0 : that.maxScrollY;
}
that.distX += deltaX;
that.distY += deltaY;
that.absDistX = m.abs(that.distX);
that.absDistY = m.abs(that.distY);
if (that.absDistX < 6 && that.absDistY < 6) {
return;
}
// Lock direction
if (that.options.lockDirection) {
if (that.absDistX > that.absDistY + 5) {
newY = that.y;
deltaY = 0;
} else if (that.absDistY > that.absDistX + 5) {
newX = that.x;
deltaX = 0;
}
}
that.moved = true;
that._pos(newX, newY);
that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
if (timestamp - that.startTime > 300) {
that.startTime = timestamp;
that.startX = that.x;
that.startY = that.y;
}
if (that.options.onScrollMove) that.options.onScrollMove.call(that, e);
},
_end: function (e) {
if (hasTouch && e.touches.length != 0) return;
var that = this,
point = hasTouch ? e.changedTouches[0] : e,
target, ev,
momentumX = { dist:0, time:0 },
momentumY = { dist:0, time:0 },
duration = (e.timeStamp || Date.now()) - that.startTime,
newPosX = that.x,
newPosY = that.y,
newDuration;
that._unbind(MOVE_EV);
that._unbind(END_EV);
that._unbind(CANCEL_EV);
if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e);
if (!that.moved) {
if (hasTouch) {
// Find the last touched element
target = point.target;
while (target.nodeType != 1) target = target.parentNode;
if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
ev = document.createEvent('MouseEvents');
ev.initMouseEvent('click', true, true, e.view, 1,
point.screenX, point.screenY, point.clientX, point.clientY,
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
0, null);
ev._fake = true;
target.dispatchEvent(ev);
}
}
that._resetPos(200);
if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
return;
}
if (duration < 300 && that.options.momentum) {
momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX;
momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y : 0), that.options.bounce ? that.wrapperH : 0) : momentumY;
newPosX = that.x + momentumX.dist;
newPosY = that.y + momentumY.dist;
if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 };
if ((that.y > 0 && newPosY > 0) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 };
}
if (momentumX.dist || momentumY.dist) {
newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);
that.scrollTo(mround(newPosX), mround(newPosY), newDuration);
if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
return;
}
that._resetPos(200);
if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
},
_resetPos: function (time) {
var that = this,
resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x,
resetY = that.y >= 0 || that.maxScrollY > 0 ? 0 : that.y < that.maxScrollY ? that.maxScrollY : that.y;
if (resetX == that.x && resetY == that.y) {
if (that.moved) {
if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end
that.moved = false;
}
return;
}
that.scrollTo(resetX, resetY, time || 0);
},
_mouseout: function (e) {
var t = e.relatedTarget;
if (!t) {
this._end(e);
return;
}
while (t = t.parentNode) if (t == this.wrapper) return;
this._end(e);
},
_transitionEnd: function (e) {
var that = this;
if (e.target != that.scroller) return;
that._unbind('webkitTransitionEnd');
that._startAni();
},
/**
*
* Utilities
*
*/
_startAni: function () {
var that = this,
startX = that.x, startY = that.y,
startTime = Date.now(),
step, easeOut,
animate;
if (that.animating) return;
if (!that.steps.length) {
that._resetPos(400);
return;
}
step = that.steps.shift();
if (step.x == startX && step.y == startY) step.time = 0;
that.animating = true;
that.moved = true;
if (that.options.useTransition) {
that._transitionTime(step.time);
that._pos(step.x, step.y);
that.animating = false;
if (step.time) that._bind('webkitTransitionEnd');
else that._resetPos(0);
return;
}
animate = function () {
var now = Date.now(),
newX, newY;
if (now >= startTime + step.time) {
that._pos(step.x, step.y);
that.animating = false;
if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end
that._startAni();
return;
}
now = (now - startTime) / step.time - 1;
easeOut = m.sqrt(1 - now * now);
newX = (step.x - startX) * easeOut + startX;
newY = (step.y - startY) * easeOut + startY;
that._pos(newX, newY);
if (that.animating) that.aniTime = nextFrame(animate);
};
animate();
},
_transitionTime: function (time) {
this.scroller.style[vendor + 'TransitionDuration'] = time + 'ms';
},
_momentum: function (dist, time, maxDistUpper, maxDistLower, size) {
var deceleration = 0.0006,
speed = m.abs(dist) / time,
newDist = (speed * speed) / (2 * deceleration),
newTime = 0, outsideDist = 0;
// Proportinally reduce speed if we are outside of the boundaries
if (dist > 0 && newDist > maxDistUpper) {
outsideDist = size / (6 / (newDist / speed * deceleration));
maxDistUpper = maxDistUpper + outsideDist;
speed = speed * maxDistUpper / newDist;
newDist = maxDistUpper;
} else if (dist < 0 && newDist > maxDistLower) {
outsideDist = size / (6 / (newDist / speed * deceleration));
maxDistLower = maxDistLower + outsideDist;
speed = speed * maxDistLower / newDist;
newDist = maxDistLower;
}
newDist = newDist * (dist < 0 ? -1 : 1);
newTime = speed / deceleration;
return { dist: newDist, time: mround(newTime) };
},
_offset: function (el) {
var left = -el.offsetLeft,
top = -el.offsetTop;
while (el = el.offsetParent) {
left -= el.offsetLeft;
top -= el.offsetTop;
}
return { left: left, top: top };
},
_bind: function (type, el, bubble) {
(el || this.scroller).addEventListener(type, this, !!bubble);
},
_unbind: function (type, el, bubble) {
(el || this.scroller).removeEventListener(type, this, !!bubble);
},
/**
*
* Public methods
*
*/
destroy: function () {
var that = this;
that.scroller.style[vendor + 'Transform'] = '';
// Remove the event listeners
that._unbind(RESIZE_EV, window);
that._unbind(START_EV);
that._unbind(MOVE_EV);
that._unbind(END_EV);
that._unbind(CANCEL_EV);
that._unbind('mouseout', that.wrapper);
if (that.options.useTransition) that._unbind('webkitTransitionEnd');
if (that.options.onDestroy) that.options.onDestroy.call(that);
},
refresh: function () {
var that = this,
offset;
that.wrapperW = that.wrapper.clientWidth;
that.wrapperH = that.wrapper.clientHeight;
that.scrollerW = that.scroller.offsetWidth;
that.scrollerH = that.scroller.offsetHeight;
that.maxScrollX = that.wrapperW - that.scrollerW;
that.maxScrollY = that.wrapperH - that.scrollerH;
that.dirX = 0;
that.dirY = 0;
that.hScroll = that.options.hScroll && that.maxScrollX < 0;
that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH);
offset = that._offset(that.wrapper);
that.wrapperOffsetLeft = -offset.left;
that.wrapperOffsetTop = -offset.top;
that.scroller.style[vendor + 'TransitionDuration'] = '0';
that._resetPos(200);
},
scrollTo: function (x, y, time, relative) {
var that = this,
step = x,
i, l;
that.stop();
if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }];
for (i=0, l=step.length; i<l; i++) {
if (step[i].relative) { step[i].x = that.x - step[i].x; step[i].y = that.y - step[i].y; }
that.steps.push({ x: step[i].x, y: step[i].y, time: step[i].time || 0 });
}
that._startAni();
},
scrollToElement: function (el, time) {
var that = this, pos;
el = el.nodeType ? el : that.scroller.querySelector(el);
if (!el) return;
pos = that._offset(el);
pos.left += that.wrapperOffsetLeft;
pos.top += that.wrapperOffsetTop;
pos.left = pos.left > 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left;
pos.top = pos.top > 0 ? 0 : pos.top < that.maxScrollY ? that.maxScrollY : pos.top;
time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time;
that.scrollTo(pos.left, pos.top, time);
},
disable: function () {
this.stop();
this._resetPos(0);
this.enabled = false;
// If disabled after touchstart we make sure that there are no left over events
this._unbind(MOVE_EV);
this._unbind(END_EV);
this._unbind(CANCEL_EV);
},
enable: function () {
this.enabled = true;
},
stop: function () {
cancelFrame(this.aniTime);
this.steps = [];
this.moved = false;
this.animating = false;
}
};
if (typeof exports !== 'undefined') exports.iScroll = iScroll;
else window.iScroll = iScroll;
})();

2015
ext-lib/iscroll-probe.js Executable file

File diff suppressed because it is too large Load Diff

2172
ext-lib/iscroll-zoom.js Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
.magazine { .magazine {
left: 150px; left: 150px;
margin-left: 0px; margin-left: 0px;
background-color: black;
} }
.page { .page {
@ -89,6 +90,9 @@
<script src="ext-lib/jszip-inflate.js"></script> <script src="ext-lib/jszip-inflate.js"></script>
<!--script src="ext-lib\jszip-deflate.js"></script--> <!--script src="ext-lib\jszip-deflate.js"></script-->
<script src="ext-lib/iscroll.js"></script>
<script src="ext-lib/iscroll-zoom.js"></script>
<script src="lib/jli.js"></script> <script src="lib/jli.js"></script>
<script src="lib/scroller.js"></script> <script src="lib/scroller.js"></script>
<script src="lib/keyboard.js"></script> <script src="lib/keyboard.js"></script>
@ -242,8 +246,90 @@ function handleFileSelect(evt) {
} }
} }
// XXX these stubs override the magazine API and use iScroll...
$(document).ready(function(){ // XXX need to account for page align...
// XXX this works only for big pages...
function getVisiblePage(){
var offset = $('.magazine').offset().left
var width = $('.viewer').width()
var pages = $('.page')
// filter the closest page to the viewer...
.filter(function(){
var page = $(this)
var p = page.position().left + offset
return p >= 0 && p < width/2
|| p < 0 && p + page.width() >= width/2
})
// select the best candidate...
// XXX
return pages
}
/************************************************ iScroll5 Actions ***/
// XXX next/prev page work but only when BOTH:
// - MagazineScroller.zoom is set to 1
// - current page is focused
// XXX zoom != 1 messes up both iScroll5 navigation AND PortableMag navigation...
// XXX we do not need the snap feature, flick event is enough to do next/prev page...
// XXX make centered page account for page align...
function enableFreeScroll(){
MagazineScroller.options.scrollY = true
MagazineScroller.options.freeScroll = true
MagazineScroller.refresh()
}
function disableFreeScroll(){
MagazineScroller.options.scrollY = false
MagazineScroller.options.freeScroll = false
MagazineScroller.refresh()
}
// Actions...
// XXX this needs correct reference point calc...
function zoomTo(n, scale, time){
// calculate page projection center...
var page = setCurrent(n)
var sc = getMagazineScale()
var st = scale
var d = st / sc
var W = $('.viewer').width()
var w = page.width()*sc
var o = page.offset().left
var C = W/2
var c = o + w/2
// left border -> 0
// centered -> C = c -> independent of d
// right border -> W
console.log('>>>', c, C, d, (c-C)*d)
// XXX this works iff d == 2
MagazineScroller.zoom(scale, C + (c-C)*d, null, time)
//return page
}
/*********************************************************************/
$(function(){
// this is to fix some sort of bug baking things align in a wrong // this is to fix some sort of bug baking things align in a wrong
// way at startup... // way at startup...
@ -259,18 +345,166 @@ $(document).ready(function(){
$(document) $(document)
.keydown(makeKeyboardHandler(KEYBOARD_CONFIG)) .keydown(makeKeyboardHandler(KEYBOARD_CONFIG))
window.MagazineScroller = makeScrollHandler($('.viewer'), { window.MagazineScroller = new IScroll('.viewer', {
hScroll: true, tap: true,
vScroll: false,
// XXX still a bit flacky... scrollX: true,
preCallback: function(){stopAnimation($('.magazine'))}, scrollY: false,
// XXX...
//scrollCallback: function(){ updateNavigator() }, scrollbars: true,
//enableMultiClicks: true, fadeScrollbars: true,
transitionEasing: 'cubic-bezier(0.33,0.66,0.66,1)', interactiveScrollbars: true,
}).start() shrinkScrollbars: 'clip',
//bounce: false,
zoom: true,
// XXX calc these dynamically depending on viewer size/resolution...
zoomMin: 0.2,
zoomMax: 2,
})
window.MULTI_FINGER_RELEASE_TIMEOUT = 50
$('.viewer') $('.viewer')
.on('tap', function(){
// XXX for some reason this does not focus the page clicked
// if it's not the current and we are in page view...
focusPage(event.target, togglePageView('?') == 'on' ? 'auto' : 'center')
handleCaption(event.target)
})
.on('touchstart', function(){
// This is here to recover from touching a finger after
// lifting it before the timeout...
if(event.touches.length > 2){
delete window._THIRD_FINGER_LIFTED
}
if(event.touches.length > 1){
delete window._SECOND_FINGER_LIFTED
}
})
.on('mouseup touchend', function(){
var now = Date.now()
// handle the touchend only if ALL fingers are lifted...
if(event.touches != null){
// count touches within a timeout to handle multytouch swipes...
if(event.touches.length == 2){
window._THIRD_FINGER_LIFTED = now
} else if(event.touches.length == 1){
window._SECOND_FINGER_LIFTED = now
}
// if we are still touching skip any action...
if(event.touches.length > 0){
return
}
}
// three finger action...
if(window._THIRD_FINGER_LIFTED != null
&& now - _THIRD_FINGER_LIFTED <= MULTI_FINGER_RELEASE_TIMEOUT){
// first/last pages...
if(MagazineScroller.directionX > 0){
setTimeout(last, 0)
} else if(MagazineScroller.directionX < 0){
setTimeout(first, 0)
}
// two finger action...
} else if(window._SECOND_FINGER_LIFTED != null
&& now - _SECOND_FINGER_LIFTED <= MULTI_FINGER_RELEASE_TIMEOUT){
// next/prev cover...
if(MagazineScroller.directionX > 0){
setTimeout(nextCover, 0)
} else if(MagazineScroller.directionX < 0){
setTimeout(prevCover, 0)
}
// single finger action...
} else {
if(togglePageView('?') == 'on'){
// next/prev page...
if(MagazineScroller.directionX > 0){
setTimeout(nextPage, 0)
} else if(MagazineScroller.directionX < 0){
setTimeout(prevPage, 0)
}
} else {
setCurrent()
}
}
})
/*
// XXX do we really need this???
window.MagazineScroller
.on('flick', function(){
if(togglePageView('?') == 'on'){
if(this.directionX > 0){
setTimeout(nextPage, 0)
} else if(this.directionX < 0){
setTimeout(prevPage, 0)
}
}
})
*/
window.MagazineScroller
.on('scrollCancel', function(){
if(togglePageView('?') == 'on'){
focusPage(setCurrent(centeredPage()))
}
})
// XXX is this needed???
window.MagazineScroller
.on('scrollEnd', function(){
// focus page that we end up on...
if(!togglePageView('?') == 'on'){
setCurrent()
}
})
// zooming...
// XXX needs testing on device...
window.MagazineScroller
.on('zoomStart', function(){
window._START_SCALE = getMagazineScale()
})
window.MagazineScroller
.on('zoomEnd', function(){
var s = getMagazineScale()
// XXX add issue support...
//var state = togglePageView('?')
// zoom in...
if(window._START_SCALE > s){
togglePageView('off')
// zoom out...
} else {
togglePageView('on')
}
})
$('.magazine')
.css({
'-webkit-transform-origin': '0px 50%',
'left': 0,
})
$('.viewer')
.on('mouseup tapup', function(){
//MagazineScroller.scrollToElement(getVisiblePage()[0], 300, true, true)
})
/*
.on('scrollCancelled', function(){ setCurrentPage() }) .on('scrollCancelled', function(){ setCurrentPage() })
.on('shortClick', handleCaption) .on('shortClick', handleCaption)
.on('shortClick', handleClick) .on('shortClick', handleClick)
@ -279,6 +513,7 @@ $(document).ready(function(){
.on('swipeRight', handleSwipeRight) .on('swipeRight', handleSwipeRight)
.on('swipeUp swipeDown', function(){ togglePageView('off') }) .on('swipeUp swipeDown', function(){ togglePageView('off') })
.on('screenReleased', handleScrollRelease) .on('screenReleased', handleScrollRelease)
*/
.on('pageChanged', updatePageNumberIndicator) .on('pageChanged', updatePageNumberIndicator)
.on('magazineDataLoaded', loadMagazineChrome) .on('magazineDataLoaded', loadMagazineChrome)
@ -299,26 +534,28 @@ $(document).ready(function(){
togglePageView('on') togglePageView('on')
// XXX this still depends on touchSwipe... // XXX this still depends on touchSwipe...
setupNavigator() //setupNavigator()
loadMagazineChrome() //loadMagazineChrome()
setCurrentPage(0) //setCurrentPage(0)
toggleThemes('none') //toggleThemes('none')
setupEditor() //setupEditor()
$('.dpi').text(getDPI()) $('.dpi').text(getDPI())
// hide the splash... // hide the splash...
setTimeout(function(){ setTimeout(function(){
//setCurrentPage(0)
focusPage(0, null, 0)
$('.splash').fadeOut() $('.splash').fadeOut()
}, 350) }, 350)
// remove the spinner... // remove the spinner...
setTimeout(function(){ setTimeout(function(){
$('#spinner').spin(false) $('#spinner').spin(false)
MagazineScroller.refresh()
}, 500) }, 500)
}) })
@ -944,7 +1181,6 @@ $(document).ready(function(){
</div> </div>
<!-- Magazine Viewer (end) ------------------------------------------> <!-- Magazine Viewer (end) ------------------------------------------>
</div> </div>
<!-- Magazine Chrome (end) --> <!-- Magazine Chrome (end) -->

View File

@ -138,17 +138,23 @@ var KEYBOARD_CONFIG = {
} }
}, },
Home: firstPage, //Home: firstPage,
End: lastPage, //End: lastPage,
Home: function(){ first() },
End: function(){ last() },
Left: { Left: {
default: function(){ prevPage() }, //default: function(){ prevPage() },
shift: prevBookmark, default: function(){ prev() },
ctrl: prevArticle, //shift: prevBookmark,
//ctrl: prevArticle,
ctrl: function(){ prevCover() },
}, },
Right: { Right: {
default: function(){ nextPage() }, //default: function(){ nextPage() },
shift: nextBookmark, default: function(){ next() },
ctrl: nextArticle, //shift: nextBookmark,
//ctrl: nextArticle,
ctrl: function(){ nextCover() },
}, },
Space: { Space: {
default: 'Right', default: 'Right',

405
layout.js
View File

@ -4,8 +4,6 @@
* *
**********************************************************************/ **********************************************************************/
var SNAP_TO_PAGES_IN_RIBBON = false
var DEFAULT_TRANSITION_DURATION = 200 var DEFAULT_TRANSITION_DURATION = 200
var INNERTIA_SCALE = 0.25 var INNERTIA_SCALE = 0.25
@ -23,61 +21,6 @@ var toggleThemes = createCSSClassToggler('.chrome', [
]) ])
// NOTE: this should not change anything unless the screen size changes...
function fitScreenSizedPages(){
var s = getPageTargetScale(1)
var W = $('.viewer').width()
$(SCREEN_SIZED_PAGES).width(W / s)
}
var togglePageFitMode = createCSSClassToggler(
'.chrome',
'page-fit-to-viewer',
function(action){
if(action == 'on'){
var n = getPageNumber()
var scale = getMagazineScale()
$(RESIZABLE_PAGES)
.width($('.viewer').width() / scale)
} else {
var n = getPageNumber()
$(RESIZABLE_PAGES).width('')
}
fitScreenSizedPages()
setCurrentPage(n)
})
var togglePageView = createCSSClassToggler(
'.chrome',
'full-page-view-mode',
function(action){
var view = $('.viewer')
var page = $('.page')
// XXX
setTransitionDuration($('.magazine'), 0)
var n = getPageNumber()
if(action == 'on'){
var scale = getPageTargetScale(1).value
setMagazineScale(scale)
//unanimated($('.magazine, .viewer'), togglePageFitMode)('on')
togglePageFitMode('on')
$('.viewer').trigger('fullScreenMode')
} else {
//unanimated($('.magazine, .viewer'), togglePageFitMode)('off')
togglePageFitMode('off')
var scale = getPageTargetScale(PAGES_IN_RIBBON).value
setMagazineScale(scale)
$('.viewer').trigger('ribbonMode')
}
// NOTE: can't disable transitions on this one because ScrollTo
// uses jQuery animation...
setCurrentPage(n)
})
// XXX this is neither final nor usable... // XXX this is neither final nor usable...
function prepareInlineCaptions(){ function prepareInlineCaptions(){
$('.page img[title]').each(function(){ $('.page img[title]').each(function(){
@ -90,30 +33,12 @@ function prepareInlineCaptions(){
/************************************************** event handlers ***/ /************************************************** event handlers ***/
// Click
// - in full page do the default click, if clicked on other page, select
// - in ribbon, open clicked page in full mode
function handleClick(evt, data){
var target = getPageNumber(data.orig_event.target)
if(target != -1){
var mag = $('.magazine')
if(togglePageView('?') == 'on'){
setTransitionDuration(mag, DEFAULT_TRANSITION_DURATION)
} else {
togglePageView('on')
}
setCurrentPage(target)
//setTransitionEasing(mag, 'ease')
setTransitionEasing(mag, 'cubic-bezier(0.33,0.66,0.66,1)')
}
}
// Click on caption... // Click on caption...
// XXX add inline caption support... // XXX add inline caption support...
function handleCaption(evt, data){ function handleCaption(elem){
elem = $(data.orig_event.target) //elem = $(data.orig_event.target)
elem = elem == null ? $('.current.page').find('.caption') : $(elem)
if(elem.is('.image-fit-height, .image-fit, .image-with-caption') if(elem.is('.image-fit-height, .image-fit, .image-with-caption')
|| elem.parents('.image-fit-height, .image-fit, .image-with-caption').length > 0){ || elem.parents('.image-fit-height, .image-fit, .image-with-caption').length > 0){
@ -136,328 +61,6 @@ function handleCaption(evt, data){
} }
// Long Click
// - in full page, go to ribbon
// - in ribbon, center clicked page
function handleLongClick(evt, data){
var target = getPageNumber(data.orig_event.target)
if(target != -1){
var mag = $('.magazine')
if(togglePageView('?') == 'on'){
togglePageView('off')
} else {
setTransitionDuration(mag, DEFAULT_TRANSITION_DURATION)
}
setCurrentPage(target)
//setTransitionEasing(mag, 'ease')
setTransitionEasing(mag, 'cubic-bezier(0.33,0.66,0.66,1)')
}
}
// Swipe Left/Right
// - in full page, next/prev page select
// - in ribbon, kinetic scroll
// - with two fingers, select next/prev article
function makeSwipeHandler(actionA, actionB){
return function(evt, data){
// ribbon mode...
if(togglePageView('?') == 'off'){
// article navigation...
if(data.touches >= 2){
actionB($('.current.page'))
return
}
// this makes things snap...
if(SNAP_TO_PAGES_IN_RIBBON){
setCurrentPage()
return
}
return handleScrollRelease(evt, data)
}
// full page view...
var mag = $('.magazine')
//setTransitionEasing(mag, 'ease-out')
setTransitionEasing(mag, 'cubic-bezier(0.33,0.66,0.66,1)')
if(data.touches >= 2){
actionB($('.current.page'))
} else {
actionA($('.current.page'))
}
}
}
var handleSwipeLeft = makeSwipeHandler(prevPage, prevArticle)
var handleSwipeRight = makeSwipeHandler(nextPage, nextArticle)
// XXX
GLOBAL_SCROLL_CALLBACK = null
// Scroll Release
// - check bounds and if out center first/last page
// - filter out "throw" speeds below threshold
// - do inertial scroll (within check bounds)
// - snap to pages
//
// NOTE: this will also handle swipeUp/swopeDown as we do not
// explicitly bind them...
// NOTE: at this point this ONLY handles horizontal scroll...
//
// XXX restore all the changed values...
// XXX this may kill the ipad...
function handleScrollRelease(evt, data){
console.log(callback)
var callback = GLOBAL_SCROLL_CALLBACK
var speed = data.speed.x
var pages = $('.page')
var mag = $('.magazine')
// innertial scroll...
// XXX make this generic...
//var t = DEFAULT_TRANSITION_DURATION * (1+Math.abs(speed))
var t = DEFAULT_TRANSITION_DURATION
// XXX this is only horizontal at this point...
var at = getElementShift(mag).left
var d = MAX_DISTANCE_TO_SCROLL != null ? MAX_DISTANCE_TO_SCROLL : Infinity
var s = sign(speed) >= 0 ? 1 : -1
var to = (at + (Math.min(Math.abs(t*speed*INNERTIA_SCALE), d) * s))
var first = getMagazineOffset(pages.first(), null, 'center')
var last = getMagazineOffset(pages.last(), null, 'center')
var easing = 'cubic-bezier(0.33,0.66,0.66,1)'
// filter out really small speeds...
if(Math.abs(speed) > 0.5){
// check bounds...
// NOTE: need to cut the distance and time if we are going the
// hit the bounds...
if(to > first){
// trim the time proportionally...
var _t = t
t = Math.abs(t * ((at-first)/(at-to)))
to = first
//easing = 'linear'
} else if(to < last){
// trim the time proportionally...
var _t = t
t = Math.abs(t * ((at-last)/(at-to)))
to = last
//easing = 'linear'
} else {
//easing = 'ease-out'
}
animateElementTo(mag, to, t, easing, speed, callback)
// check scroll bounds...
// do not let the user scroll out of view...
} else {
if(at > first){
//animateElementTo(mag, first, DEFAULT_TRANSITION_DURATION, 'ease-in')
animateElementTo(mag, first,
DEFAULT_TRANSITION_DURATION,
easing,
null,
callback)
} else if(at < last){
//animateElementTo(mag, last, DEFAULT_TRANSITION_DURATION, 'ease-in')
animateElementTo(mag, last,
DEFAULT_TRANSITION_DURATION,
easing,
null,
callback)
}
}
}
/********************************************************* helpers ***/
function getPageInMagazineOffset(page, scale){
if(page == null){
page = $('.current.page')
} else if(typeof(page) == typeof(7)){
page = $($('.page')[page])
}
return page.position().left / (scale == null ? getMagazineScale() : scale)
}
// XXX there is something here that depends on scale that is either not
// compensated, or is over compensated...
function getMagazineOffset(page, scale, align){
if(page == null){
page = $('.current.page')
// if no current page get the first...
if(page.length == 0){
page = $('.page').first()
}
}
if(scale == null){
scale = getMagazineScale()
}
if(align == null){
align = getPageAlign(page)
}
var mag = $('.magazine')
// calculate the align offset...
if(align == 'left'){
var offset = 0
} else if(align == 'right'){
var offset = $('.viewer').width() - page.width()*scale
// center (default)
} else {
var offset = $('.viewer').width()/2 - (page.width()/2)*scale
}
// NOTE: this without scaling also represents the inner width of
// the viewer...
var w = mag.outerWidth(true)
// XXX this depends on scale...
//var pos = getPageInMagazineOffset(page, scale)
var pos = page.position().left//*scale
var l = 0
return -((w - w*scale)/2 + pos) + offset
}
function getPageNumber(page){
// a page/element is given explicitly...
if(page != null){
page = $(page)
if(!page.hasClass('page')){
page = page.parents('.page')
}
return $('.page').index(page)
}
// current page index...
if(togglePageView('?') == 'on'){
return $('.page').index($('.current.page'))
// get the closest page to view center...
// NOTE: this ignores page aligns and only gets the page who's center
// is closer to view's center
} else {
var scale = getMagazineScale()
var o = -$($('.magazine')[0]).offset().left - $('.viewer').offset().left
var W = $('.viewer').width()
var cur = -1
var res = $('.page').map(function(i, e){
e = $(e)
var l = e.position().left
var w = e.width()*scale
return Math.abs((l+(w/2)) - (o+(W/2)))
}).toArray()
cur = res.indexOf(Math.min.apply(Math, res))
return cur
}
}
function getMagazineScale(){
return getElementScale($('.magazine'))
}
function setMagazineScale(scale){
var mag = $('.magazine')
var cur = $('.current.page')
if(cur.length == 0){
cur = $('.page').first()
}
// center-align ribbon view pages...
var align = togglePageView('?') == 'off' ? 'center' : null
var left = getMagazineOffset(cur, scale, align)
setElementTransform(mag, left, scale)
return mag
}
/********************************************************* actions ***/
function setCurrentPage(n, use_transitions){
if(n == null){
n = getPageNumber()
}
if(typeof(n) != typeof(3)){
n = getPageNumber(n)
}
var l = $('.page').length
// normalize the number...
n = n < 0 ? l - n : n
n = n < -l ? 0 : n
n = n >= l ? l - 1 : n
use_transitions = use_transitions != null ?
use_transitions
: USE_TRANSITIONS_FOR_ANIMATION
$('.current.page').removeClass('current')
$($('.page')[n]).addClass('current')
var cur = $('.current.page')
// center-align pages in ribbon view...
var align = togglePageView('?') == 'off' ? 'center' : null
var left = getMagazineOffset(cur, null, align)
if(use_transitions){
setElementTransform($('.magazine'), left)
} else {
animateElementTo($('.magazine'), left)
}
$('.viewer').trigger('pageChanged', n)
$(':focus').blur()
return cur
}
function nextPage(page){
// XXX is this the right place for this?
setTransitionDuration($('.magazine'), DEFAULT_TRANSITION_DURATION)
setCurrentPage(getPageNumber(page)+1)
}
function prevPage(page){
// XXX is this the right place for this?
setTransitionDuration($('.magazine'), DEFAULT_TRANSITION_DURATION)
var n = getPageNumber(page)-1
n = n < 0 ? 0 : n
setCurrentPage(n)
}
function firstPage(){
// XXX is this the right place for this?
setTransitionDuration($('.magazine'), DEFAULT_TRANSITION_DURATION)
setCurrentPage(0)
}
function lastPage(){
// XXX is this the right place for this?
setTransitionDuration($('.magazine'), DEFAULT_TRANSITION_DURATION)
setCurrentPage(-1)
}
/********************************************************************** /**********************************************************************
* vim:set ts=4 sw=4 : */ * vim:set ts=4 sw=4 : */

View File

@ -55,6 +55,12 @@ var FULL_HISTORY_ENABLED = false
// if true, use CSS3 transforms to scroll, of false, use left. // if true, use CSS3 transforms to scroll, of false, use left.
var USE_TRANSFORM = true var USE_TRANSFORM = true
var SCROLL_TIME = 400
var BOUNCE_LENGTH = 10
var BOUNCE_TIME_DIVIDER = 5
// list of css classes of pages that will not allow page fitting. // list of css classes of pages that will not allow page fitting.
var NO_RESIZE_CLASSES = [ var NO_RESIZE_CLASSES = [
'no-resize', 'no-resize',
@ -119,20 +125,23 @@ var _PAGE_VIEW
// - single page mode (.page-view-mode) // - single page mode (.page-view-mode)
// - thumbnail/ribbon mode // - thumbnail/ribbon mode
var togglePageView = createCSSClassToggler( var togglePageView = createCSSClassToggler(
'.viewer', '.chrome',
'page-view-mode', 'page-view-mode',
// post-change callback... // post-change callback...
function(action){ function(action){
if(action == 'on'){ if(action == 'on'){
fitNPages(1, !FIT_PAGE_TO_VIEW) fitNPages(1, !FIT_PAGE_TO_VIEW)
MagazineScroller.options.momentum = false
_PAGE_VIEW = true _PAGE_VIEW = true
} else { } else {
fitNPages(PAGES_IN_RIBBON) fitNPages(PAGES_IN_RIBBON)
MagazineScroller.options.momentum = true
_PAGE_VIEW = false _PAGE_VIEW = false
} }
}) })
// this will simply update the current view... // this will simply update the current view...
function updateView(){ function updateView(){
return togglePageView(togglePageView('?')) return togglePageView(togglePageView('?'))
@ -142,6 +151,68 @@ function updateView(){
/********************************************************* helpers ***/ /********************************************************* helpers ***/
function centeredPageNumber(){
var scale = getMagazineScale()
var o = -$($('.magazine')[0]).offset().left - $('.viewer').offset().left
var W = $('.viewer').width()
var cur = -1
var res = $('.page').map(function(i, e){
e = $(e)
var l = e.position().left
var w = e.width()*scale
return Math.abs((l+(w/2)) - (o+(W/2)))
}).toArray()
cur = res.indexOf(Math.min.apply(Math, res))
return cur
}
function centeredPage(){
return $('.page').eq(centeredPageNumber())
}
function visiblePages(partial){
var W = $('.viewer').width()
var scale = getMagazineScale()
return $('.page').filter(function(_, page){
page = $(page)
// XXX this calculates the offset from the document rather than the magazine...
var o = page.offset().left
// page out of view (right)...
if(o >= W){
return false
}
var w = page.width() * scale
if(o < 0){
// partial left...
if(partial && o + w > 0){
return true
}
// page out of view (left)...
return false
}
// partial right...
if(partial && W - o < w){
return true
}
// page compleately in view...
if(W - o >= w){
return true
}
// NOTE: we should not get here but just in case...
return false
})
}
function isPageResizable(page){ function isPageResizable(page){
if(page == null){ if(page == null){
page = $('.current.page') page = $('.current.page')
@ -223,35 +294,64 @@ function getMagazineTitle(){
function getMagazineScale(){ function getMagazineScale(){
return getElementScale($('.scaler')) return getElementScale($('.magazine'))
} }
function setPageScale(scale){ function setMagazineScale(scale){
return setElementTransform($('.scaler'), null, scale) var mag = $('.magazine')
var cur = $('.current.page')
if(cur.length == 0){
cur = $('.page').first()
}
// center-align ribbon view pages...
var align = togglePageView('?') == 'off' ? 'center' : null
var left = getMagazineOffset(cur, scale, align)
//setElementTransform(mag, left, scale)
MagazineScroller.zoom(scale)
return mag
} }
// NOTE: if page is not given get the current page number... // NOTE: if page is not given get the current page number...
function getPageNumber(page){ function getPageNumber(page){
if(page == null){ // a page/element is given explicitly...
page = $('.current.page') if(page != null){
} else {
page = $(page) page = $(page)
if(!page.hasClass('page')){
page = page.parents('.page')
}
return $('.page').index(page)
}
// current page index...
if(togglePageView('?') == 'on'){
return $('.page').index($('.current.page'))
// get the closest page to view center...
// NOTE: this ignores page aligns and only gets the page who's center
// is closer to view's center
} else {
return centeredPageNumber()
} }
return $('.page').index(page)
} }
// NOTE: negative values will yield results from the tail... // NOTE: negative values will yield results from the tail...
function getPageAt(n){ function getPageAt(n){
var page = $('.page') var page = $('.page')
if(n < 0){ if(n < 0){
n = page.length + n n = page.length + n
} }
return $(page[n]) return $(page).eq(n)
} }
function shiftMagazineTo(offset){ function shiftMagazineTo(offset){
setElementTransform($('.magazine'), offset) MagazineScroller.scrollTo(offset, 0, 200)
//setElementTransform($('.magazine'), offset)
} }
// XXX this is almost the same as getElementScale... // XXX this is almost the same as getElementScale...
@ -386,373 +486,374 @@ function viewResizeHandler(){
} }
// swipe state handler
// this handles single and double finger swipes and dragging
// while draggign this triggers magazineDragging event on the viewer...
// NOTE: this will trigger 'magazineDragging' event on the viewer on
// each call while dragging...
// XXX for some reason with finger count of 3 and greater, touchSwipe
// dies on android...
function makeSwipeHandler(){
var pages
var cur
var n
var scale
var mag
var pos
var viewer = $('.viewer')
return function(evt, phase, direction, distance, duration, fingers){
if(phase == 'start'){
// NOTE: this is used with the "unanimated" trick, we will make
// dragging real-time...
togglePageDragging('on')
// setup the data for the drag...
pages = $('.page')
cur = $('.current.page')
n = pages.index(cur)
scale = getMagazineScale()
mag = $('.magazine')
pos = $('.navigator .bar .indicator')
// XXX make this drag pages that are larger than view before dragging outside...
} else if(phase=='move'
// see if wee need to drag the page and allways drag the ribbon...
&& (DRAG_FULL_PAGE || !_PAGE_VIEW)
&& (direction=='left' || direction=='right')){
if(direction == 'left'){
shiftMagazineTo(-cur.position().left/scale - distance/scale)
} else if(direction == 'right') {
shiftMagazineTo(-cur.position().left/scale + distance/scale)
}
viewer.trigger('magazineDragging')
} else if(phase == 'cancel'){
togglePageDragging('off')
setCurrentPage()
} else if(phase =='end' ){
togglePageDragging('off')
// see which page is closer to the middle of the screen and set it...
// do this based on how much we dragged...
var p = Math.ceil((distance/scale)/cur.width())
// prev page...
if(direction == 'right'){
// 2 fingers moves to closest article...
if(fingers == 2){
prevArticle()
// 3+ fingers moves to bookmark...
} else if(fingers >= 3){
prevBookmark()
} else {
setCurrentPage(Math.max(n-p, 0))
}
// next page...
} else if(direction == 'left'){
if(fingers == 2){
nextArticle()
} else if(fingers >= 3){
nextBookmark()
} else {
setCurrentPage(Math.min(n+p, pages.length-1))
}
}
}
}
}
/********************************************************** layout ***/ /********************************************************** layout ***/
// mode can be:
// - viewer
// - content
//
// XXX should this calculate offset????
function fitPagesTo(mode, cur, time, scale){
mode = mode == null ? 'content' : mode
cur = cur == null ? centeredPageNumber() : cur
time = time == null ? 0 : time
scale = scale == null ? getMagazineScale() : scale
// fit to content...
if(mode == 'content'){
var target_width = 'auto'
var target_height = 'auto'
// fit to viewer...
} else if(mode == 'viewer'){
var viewer = $('.viewer')
var W = viewer.width()
var H = viewer.height()
// XXX is this a good way to sample content size???
var content = $('.content')
var w = content.width()
var h = content.height()
// need to calc width only...
if(W/H > w/h){
s = H/h
w = W/s
h = h
// need to calc height only...
} else if(W/H > w/h){
s = W/w
h = H/s
w = w
// set both width and height to defaults (content and page ratios match)...
} else {
s = W/h
h = h
w = w
}
var target_width = w
var target_height = h
// bad mode...
} else {
return
}
var pages = $('.page')
var offset = 0
$(RESIZABLE_PAGES)
.each(function(_, e){
var E = $(e)
var w = target_width == 'auto' ? E.find('.content').width() : target_width
var h = target_height == 'auto' ? E.find('.content').height() : target_height
// offset...
if(pages.index(e) < cur){
offset += (E.width() - w)
}
// offset half the current page...
if(pages.index(e) == cur){
// XXX to be more accurate we can use distance from page
// center rather than 1/2...
offset += ((E.width() - w)/2)
}
if(time <= 0){
e.style.width = w
e.style.height = h
} else {
E.animate({
width: w,
height: h,
}, time, 'linear')
}
})
//$(NON_RESIZABLE_PAGES).width('auto')
$(NON_RESIZABLE_PAGES)
.each(function(_, e){
e.style.width = 'auto'
})
MagazineScroller.scrollBy(offset*scale, 0, time)
setTimeout(function(){
MagazineScroller.refresh()
}, 0)
}
// NOTE: if n is not given then it defaults to 1 // NOTE: if n is not given then it defaults to 1
// NOTE: if n > 1 and fit_to_content is not given it defaults to true // NOTE: if n > 1 and fit_to_content is not given it defaults to true
// NOTE: if n is 1 then fit_to_content bool argument controls wether: // NOTE: if n is 1 then fit_to_content bool argument controls wether:
// - the page will be stretched to viewer (false) // - the page will be stretched to viewer (false)
// - or to content (true) // - or to content (true)
// XXX need to align/focus the corrent page...
// case:
// when current page is pushed off center by a resized page...
function fitNPages(n, fit_to_content){ function fitNPages(n, fit_to_content){
if(n == null){ n = n == null ? 1 : n
n = 1
}
if(n > 1 && fit_to_content == null){
fit_to_content = true
}
var view = $('.viewer')
if(USE_REAL_PAGE_SIZES){
var page = $(RESIZABLE_PAGES)
} else {
var page = $('.page')
}
var content = $('.content')
var cur = $('.current.page')
var W = view.width()
var H = view.height()
var cW = content.width()
var cH = content.height()
// to compensate for transitions, no data sampling should be beyound
// this point, as we will start changing things next...
var scale = getPageTargetScale(n, fit_to_content) var scale = getPageTargetScale(n, fit_to_content)
// cache some data... if(n == 1){
var target_width = scale.width fitPagesTo('viewer')
var target_height = scale.height
var rW = scale.result_width
// NOTE: we need to calculate the offset as the actual widths during
// the animation are not correct... so just looking at .position()
// will be counterproductive...
if(!USE_REAL_PAGE_SIZES && fit_to_content){
var offset = rW * getPageNumber()-1
} else { } else {
// calculate the target offset... fitPagesTo('content')
if(USE_REAL_PAGE_SIZES){
var rpages = $(RESIZABLE_PAGES+', .current.page')
} else {
var rpages = page
}
var i = rpages.index(cur)
var offset = rW * i-1
// now do the no-resize elements...
if(USE_REAL_PAGE_SIZES){
var nrpages = $(NON_RESIZABLE_PAGES+', .current.page')
i = nrpages.index(cur)
nrpages.splice(i)
nrpages.each(function(_, e){
offset += $(e).children('.content').width()
})
}
} }
MagazineScroller.zoom(scale)
// align the magazine... MagazineScroller.refresh()
if(USE_REAL_PAGE_SIZES){
if(!isPageResizable(cur)){
var align = getPageAlign(cur)
// center align if explicitly required or if we are in a ribbon...
if(n > 1 || align == 'center'){
rW = cur.children('.content').width()
// align left...
} else if(align == 'left'){
rW = $('.viewer').width()/scale
// align right...
} else if(align == 'right'){
var v = $('.viewer')
rW = (v.width()/scale/2 - (v.width()/scale-cur.width()))*2
}
}
}
// now do the actual modification...
// do the scaling...
setElementScale($('.scaler'), scale)
// XXX for some reason setting size "auto" will first shrink the whole
// page to 0 and then instantly set it to the correct size...
//page
// .width(target_width)
// .height(target_height)
//if(USE_REAL_PAGE_SIZES){
// $(NON_RESIZABLE_PAGES).width('auto')
//}
// NOTE: we only need the .scaler animated, the rest just plays havoc with
// the transition...
// XXX this still jumps to offset on left/right aligned pages but 1) on
// fast transitions it is not noticable and 2) it is way better than
// the effect that was before...
unanimated($('.page, .content, .magazine'), function(){
// NOTE: this is not done directly as to avoid artifacts asociated with
// setting 'auto' to all the elements, which makes them first slowly
// shrink down to 0 and then appear correctly sized in an instant.
page.each(function(_, e){
if(target_width == 'auto'){
e.style.width = $(e).find('.content').width()
e.style.height = $(e).find('.content').height()
} else {
e.style.width = target_width
e.style.height = target_height
}
})
if(USE_REAL_PAGE_SIZES){
//$(NON_RESIZABLE_PAGES).width('auto')
$(NON_RESIZABLE_PAGES).each(function(_, e){
e.style.width = 'auto'
})
}
// fix position...
setCurrentPage(null, offset, rW)
}, 200)()
} }
/********************************************************* actions ***/ /********************************************************* actions ***/
// NOTE: "width" is used ONLY to center the page. // Set the .current class...
// NOTE: if n is not given it will be set to current page number //
// NOTE: if width is not given it will be set to current page width. // page can be:
// NOTE: n can be: // - null - centered page in view
// - page number // - number - page number/index
// - page element // - page - a page element
// NOTE: this will fire a 'pageChanged(n)' event on the viewer each time // - elem - resolves to a containing page element
// it is called... function setCurrent(page){
// NOTE: this now supports negative indexes to count pages from the end... var pages = $('.page')
function setCurrentPage(n, offset, width){ page = page == null ? pages.eq(centeredPageNumber())
var page = $('.page') : typeof(page) == typeof(123) ? pages.eq(Math.max(0, Math.min(page, pages.length-1)))
// setup n and cur... : !$(page).eq(0).hasClass('page') ? $(page).eq(0).parents('.page').eq(0)
// no n is given, do the defaultdance : $(page).eq(0)
if(n == null){
var cur = $('.current.page')
// no current page...
// try to land on the magazine cover...
if(cur.length == 0){
cur = $('.magazine > .cover')
}
// try the first cover...
if(cur.length == 0){
cur = $('.cover.page')
}
// try first page...
if(cur.length == 0){
cur = page.first()
}
// no pages to work with...
if(cur.length == 0){
return
}
n = page.index(cur)
// n is a number... if(page.hasClass('current')){
} else if(typeof(n) == typeof(1)) { return page
// normalize n...
if(n >= page.length){
n = page.length-1
} else if(-n > page.length){
n = 0
}
var cur = getPageAt(n)
// n is an element, likely...
} else {
var cur = $(n)
n = $('.page').index(cur)
//n = page.index(cur)
}
// default width...
if(width == null){
width = cur.width()
if(USE_REAL_PAGE_SIZES && togglePageView('?') == 'on'){
var align = getPageAlign(cur)
var scale = getMagazineScale()
if(align == 'center'){
width = cur.width()
} else if(align == 'left'){
width = $('.viewer').width()/scale
} else if(align == 'right'){
var v = $('.viewer')
width = (v.width()/scale/2 - (v.width()/scale-cur.width()))*2
}
}
} }
// set the class...
$('.current.page').removeClass('current') $('.current.page').removeClass('current')
cur.addClass('current') return page.addClass('current')
// NOTE: this will be wrong during a transition, that's why we
// can pass the pre-calculated offset as an argument...
shiftMagazineTo(-(offset == null ?
cur.position()['left']/getMagazineScale()
: offset))
// center the pages correctly...
// NOTE: this is the main reason we need width, and we can get it
// pre-calculated because of ongoing transitions make it
// pointless to read it...
$('.magazine').css({
'margin-left': -width/2
})
$('.viewer').trigger('pageChanged', n)
return cur
} }
function goToMagazineCover(){ // Focus a page...
return setCurrentPage(0) //
// NOTE: n is a setCurrent(..) compatible value...
// NOTE: if n is out of bounds (n < 0 | n >= length) this will focus the
// first/last page and bounce...
function focusPage(n, align, time){
// XXX the default needs to depend on the scale...
align = align == null ? 'auto' : align
time = time == null ? SCROLL_TIME : time
var page = setCurrent(n)
var pages = $('.page')
align = align == 'auto' ? getPageAlign(page) : align
// magazine offset...
var m = page.position().left
// base value for 'left' align...
var o = 0
var w = page.width() * getMagazineScale()
var W = $('.viewer').width()
if(align != 'left'){
// right...
if(align == 'right'){
o = W - w
// center / default...
} else {
o = W/2 - w/2
}
}
// compensate for first/last page align to screen (optional???)...
var offset = page.offset().left
var f = pages.first().offset().left
if(f + o - offset >= 0){
o = 0
m = 0
}
var last = $('.page').last()
var l = last.offset().left
if(l + o - offset <= W - w){
o = 0
m = last.position().left + last.width()*getMagazineScale() - W
}
// do the bounce...
if(time > 0){
var i = pages.index(page)
var l = pages.length
if(n < 0){
o += BOUNCE_LENGTH*getMagazineScale()
time /= BOUNCE_TIME_DIVIDER
}
if(n >= l){
o -= BOUNCE_LENGTH*getMagazineScale()
time /= BOUNCE_TIME_DIVIDER
}
}
// NOTE: this does not care about the zoom...
MagazineScroller.scrollTo(-m + o, 0, time)
return page
} }
function goToMagazineEnd(){
return setCurrentPage(-1)
// Focus first/last page...
//
// NOTE: if we are already at the first/last page, do a bounce...
function first(align){
// visually show that we are at the start...
if($('.magazine').offset().left >= 0){
return focusPage(-1, align)
}
return focusPage(0, align)
} }
function goToArticleCover(){ function last(align){
// try and get the actual first cover... var mag = $('.magazine')
var cover = $('.current.page') var l = mag.offset().left
.parents('.article') var end = mag.offset().left + mag.width()*getMagazineScale()
.find('.cover.page') var i = $('.page').length-1
.first()
if(cover.length == 0){ if(end <= $('.viewer').width()+1){
// no cover, get the first page... return focusPage(i+1, align)
return setCurrentPage( }
$('.current.page') return focusPage(i, align)
.parents('.article') }
.find('.page')
.first())
// Focus a page of class cls adjacent to current in direction...
//
// direction can be:
// - 'next' - next page
// - 'prev' - previous page
//
// If cls is not given, then all pages (.page) are considered.
//
// NOTE: if we are already at the first/last page and direction is
// prev/next resp. then do a bounce...
function step(direction, cls, align){
cls = cls == null ? '' : cls
var page = visiblePages(true).filter('.current').eq(0)
var pages = $('.page')
if(page.length == 0){
page = setCurrent()
}
var i = pages.index(page)
var l = pages.length
// if we are at the first/last page do the bounce dance...
// bounce first...
if(i == 0 && direction == 'prev'){
return focusPage(-1, align)
}
// bounce last...
if(i == l-1 && direction == 'next'){
return focusPage(l, align)
}
var to = page[direction+'All']('.page'+cls)
// if we have no pages on the same level, to a deeper search...
if(to.length == 0){
if(direction == 'next'){
to = pages.slice(i+1).filter('.page'+cls).first()
} else {
to = pages.slice(0, i).filter('.page'+cls).last()
}
}
// still no candidates, then we can't do a thing...
if(to.length == 0){
to = page
}
return focusPage(to.eq(0), align)
}
// Focus next/prev page shorthands...
//
function nextPage(cls, align){ return step('next', cls, align) }
function prevPage(cls, align){ return step('prev', cls, align) }
// Focus next/prev cover page shorthands...
//
function nextCover(cls, align){
cls = cls == null ? '' : cls
return step('next', '.cover'+cls, align)
}
function prevCover(cls, align){
cls = cls == null ? '' : cls
return step('prev', '.cover'+cls, align)
}
// Move the view a screen width (.viewer) left/right...
//
// NOTE: if we are at magazine start/end and try to move left/right resp.
// this will do a bounce...
function nextScreen(time){
time = time == null ? SCROLL_TIME : time
var W = $('.viewer').width()
var mag = $('.magazine')
var o = mag.position().left
var w = mag.width()*getMagazineScale()
// we reached the end...
if(w + o < 2*W){
// NOTE: we use focusPage(..) to handle stuff like bounces...
return focusPage($('.page').length)
}
MagazineScroller.scrollTo(o-W, 0, time)
return setCurrent()
}
function prevScreen(time){
time = time == null ? SCROLL_TIME : time
var W = $('.viewer').width()
var o = $('.magazine').position().left
// we reached the start...
if(-o < W){
// NOTE: we use focusPage(..) to handle stuff like bounces...
return focusPage(-1)
}
MagazineScroller.scrollTo(o+W, 0, time)
return setCurrent()
}
// Mode-aware next/prev high-level actions...
//
// Supported modes:
// - page view - focus next/prev page
// - magazine view - view next/prev screen
//
function next(){
if(togglePageView('?') == 'on'){
return nextPage()
} else { } else {
return setCurrentPage(cover) return nextScreen()
} }
} }
function prev(){
if(togglePageView('?') == 'on'){
function nextPage(){ return prevPage()
var pages = $('.page') } else {
var cur = $('.current.page') return prevScreen()
return setCurrentPage(Math.min(pages.index(cur)+1, pages.length-1))
}
function prevPage(){
var pages = $('.page')
var cur = $('.current.page')
return setCurrentPage(Math.max(pages.index(cur)-1, 0))
}
function nextArticle(){
var cur = $('.current.page').parents('.article')
// we are at the magazine cover...
if(cur.length == 0){
return setCurrentPage(
$('.magazine .article .page:first-child').first())
} }
// just find the next one...
var articles = $('.magazine .article')
return setCurrentPage(
$(articles[Math.min(articles.index(cur)+1, articles.length-1)])
.find('.page')
.first())
}
function prevArticle(){
var cur = $('.current.page').parents('.article')
// we are at the magazine cover...
if(cur.length == 0){
//return $('.current.page')
return setCurrentPage()
}
// just find the prev one...
var articles = $('.magazine .article')
return setCurrentPage(
$(articles[Math.max(articles.index(cur)-1, 0)])
.find('.page')
.first())
} }
@ -809,6 +910,7 @@ function clearBookmarks(){
// NOTE: this will trigger the folowing events on the viewer: // NOTE: this will trigger the folowing events on the viewer:
// - bookmarkAdded(n) // - bookmarkAdded(n)
// - bookmarkRemoved(n) // - bookmarkRemoved(n)
// XXX rewrite...
function toggleBookmark(n){ function toggleBookmark(n){
if(n == null){ if(n == null){
n = getPageNumber() n = getPageNumber()
@ -841,21 +943,13 @@ function toggleBookmark(n){
return res return res
} }
function nextBookmark(){ function nextBookmark(cls, align){
var pages = $('.page') cls = cls == null ? '' : cls
pages = $(pages.splice(getPageNumber()+1)) return step('next', '.bookmark'+cls, align)
page = pages.children('.bookmark').first().parents('.page')
if(page.length != 0){
return setCurrentPage(page)
}
} }
function prevBookmark(){ function prevBookmark(cls, align){
var pages = $('.page') cls = cls == null ? '' : cls
pages.splice(getPageNumber()) return step('prev', '.bookmark'+cls, align)
page = pages.children('.bookmark').last().parents('.page')
if(page.length != 0){
return setCurrentPage(page)
}
} }