1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-04 15:09:16 +02:00

Added Snap helper

This commit is contained in:
Fred Chasen 2018-11-03 14:34:20 -07:00
parent 3bf150163c
commit 10b451dc1c
9 changed files with 362 additions and 160 deletions

View file

@ -11,24 +11,8 @@
body {
margin: 0;
-webkit-scroll-snap-type: mandatory;
-webkit-scroll-snap-points-x: repeat(100%);
-webkit-overflow-scrolling: auto;
/*This scroll snap functionality is part of a polyfill
that enables the functionality in Chrome.*/
scroll-snap-type: mandatory;
scroll-snap-points-x: repeat(100%);
width: 100vw;
overflow: auto;
}
.epub-container {
margin: 0;
/*-webkit-scroll-snap-type: mandatory;
-webkit-scroll-snap-points-x: repeat(100%);*/
/* -webkit-overflow-scrolling: touch; */
}
</style>
</head>
<body>
@ -42,18 +26,15 @@
// Load the opf
var book = ePub(url || "https://s3.amazonaws.com/moby-dick/");
var rendition = book.renderTo(document.body, {
// width: "100vw",
// height: "100vh",
overflow: "visible",
manager: "continuous",
// flow: "paginated"
snap: true
});
rendition.display(currentCfi || currentSectionIndex);
rendition.on("rendered", function(section){
console.log("rendered", section);
// console.log("rendered", section);
var nextSection = section.next();
var prevSection = section.prev();
@ -65,9 +46,9 @@
});
rendition.on("relocated", function(location){
console.log("locationChanged", location)
// console.log("locationChanged", location)
console.log("locationChanged start", location.start.cfi)
console.log("locationChanged end", location.end.cfi)
// console.log("locationChanged end", location.end.cfi)
});
window.addEventListener("unload", function () {
@ -76,119 +57,5 @@
});
</script>
<script>
/*
var isChrome = /Chrome/.test(navigator.userAgent);
var isWebkit = !isChrome && /AppleWebKit/.test(navigator.userAgent);
var snapWidth = window.innerWidth;
var last_known_scroll_position = 0;
var ticking = false;
var wait;
var touchCanceler = false;
var resizeCanceler = false;
var beforeTouchMovePosition = false;
if(!isWebkit) {
window.addEventListener('scroll', function(e) {
last_known_scroll_position = window.scrollX;
clearTimeout(wait);
if (!touchCanceler) {
wait = setTimeout(function() {
snap(last_known_scroll_position);
}, 100);
}
});
window.addEventListener('touchup', function(e) {
touchCanceler = false;
if (last_known_scroll_position !== beforeTouchMovePosition) {
wait = setTimeout(function() {
snap(last_known_scroll_position);
}, 100);
}
beforeTouchMovePosition = false;
});
window.addEventListener('touchmove', function(e) {
touchCanceler = true;
beforeTouchMovePosition = last_known_scroll_position;
});
window.addEventListener('resize', function(e) {
resizeCanceler = true;
snapWidth = window.innerWidth;
});
}
function snap(scroll_pos) {
var snapTo = Math.round(scroll_pos / snapWidth) * snapWidth;
if (scroll_pos % snapWidth > 0) {
scrollToX(snapTo, 20000);
}
}
function scrollToX(scrollTargetX, speed, easing) {
var scrollX = window.scrollX,
scrollTargetX = scrollTargetX || 0,
speed = speed || 2000,
easing = easing || 'easeOutSine',
currentTime = 0;
// min time .1, max time .8 seconds
var time = Math.max(.1, Math.min(Math.abs(scrollX - scrollTargetX) / speed, .8));
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
var PI_D2 = Math.PI / 2,
easingEquations = {
easeOutSine: function (pos) {
return Math.sin(pos * (Math.PI / 2));
},
easeInOutSine: function (pos) {
return (-0.5 * (Math.cos(Math.PI * pos) - 1));
},
easeInOutQuint: function (pos) {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 5);
}
return 0.5 * (Math.pow((pos - 2), 5) + 2);
}
};
// add animation loop
function tick() {
currentTime += 1 / 60;
var p = currentTime / time;
var t = easingEquations[easing](p);
if (touchCanceler) {
return;
}
if (resizeCanceler) {
resizeCanceler = false;
return;
}
if (p < 1) {
window.requestAnimationFrame(tick);
window.scrollTo(scrollX + ((scrollTargetX - scrollX) * t), 0);
} else {
window.scrollTo(scrollTargetX, 0);
}
}
tick();
}
*/
</script>
<!-- <script src="../node_modules/scrollsnap-polyfill/vendor/philipwalton/polyfill.js"></script>
<script src="../node_modules/scrollsnap-polyfill/src/scrollsnap-polyfill.js"></script> -->
</body>
</html>

View file

@ -5,8 +5,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EPUB.js Pagination Example</title>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/detect_swipe/2.1.1/jquery.detect_swipe.min.js"></script>
<!-- <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script> -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/detect_swipe/2.1.1/jquery.detect_swipe.min.js"></script> -->
<script src="../dist/epub.js"></script>
<link rel="stylesheet" type="text/css" href="examples.css">
@ -35,7 +35,7 @@
height: 96.5%;
}
#viewer iframe {
pointer-events: none;
/* pointer-events: none; */
}
.arrow {
position: inherit;
@ -56,7 +56,8 @@
manager: "continuous",
flow: "paginated",
width: "100%",
height: "100%"
height: "100%",
snap: true
});
var displayed = rendition.display("chapter_001.xhtml");
@ -94,13 +95,13 @@
}, false);
$(window).on( "swipeleft", function( event ) {
rendition.next();
});
$(window).on( "swiperight", function( event ) {
rendition.prev();
});
// $(window).on( "swipeleft", function( event ) {
// rendition.next();
// });
//
// $(window).on( "swiperight", function( event ) {
// rendition.prev();
// });
</script>

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "epubjs",
"version": "0.3.75",
"version": "0.3.78",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -814,7 +814,7 @@ class Contents {
}
DOM_EVENTS.forEach(function(eventName){
this.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
this.document.addEventListener(eventName, this.triggerEvent.bind(this), { passive: true });
}, this);
}

View file

@ -1,5 +1,6 @@
import {extend, defer, requestAnimationFrame} from "../../utils/core";
import DefaultViewManager from "../default";
import Snap from "../helpers/snap";
import { EVENTS } from "../../utils/constants";
import debounce from "lodash/debounce";
@ -17,7 +18,9 @@ class ContinuousViewManager extends DefaultViewManager {
offset: 500,
offsetDelta: 250,
width: undefined,
height: undefined
height: undefined,
snap: false,
afterScrolledTimeout: 10
});
extend(this.settings, options.settings || {});
@ -75,10 +78,10 @@ class ContinuousViewManager extends DefaultViewManager {
if(!this.isPaginated) {
distY = offset.top;
offsetY = offset.top+this.settings.offset;
offsetY = offset.top+this.settings.offsetDelta;
} else {
distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
offsetX = distX+this.settings.offset;
offsetX = distX+this.settings.offsetDelta;
}
if (distX > 0 || distY > 0) {
@ -371,6 +374,10 @@ class ContinuousViewManager extends DefaultViewManager {
}.bind(this));
this.addScrollListeners();
if (this.isPaginated && this.settings.snap) {
this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === "object") && this.settings.snap);
}
}
addScrollListeners() {
@ -455,12 +462,14 @@ class ContinuousViewManager extends DefaultViewManager {
this.scrollDeltaHorz = 0;
}.bind(this), 150);
clearTimeout(this.afterScrolled);
this.didScroll = false;
}
scrolled() {
this.q.enqueue(function() {
this.check();
}.bind(this));
@ -472,11 +481,18 @@ class ContinuousViewManager extends DefaultViewManager {
clearTimeout(this.afterScrolled);
this.afterScrolled = setTimeout(function () {
// Don't report scroll if we are about the snap
if (this.snapper && this.snapper.needsSnap()) {
return;
}
this.emit(EVENTS.MANAGERS.SCROLLED, {
top: this.scrollTop,
left: this.scrollLeft
});
}.bind(this));
}.bind(this), this.settings.afterScrolledTimeout);
}
next(){
@ -560,6 +576,14 @@ class ContinuousViewManager extends DefaultViewManager {
}
}
destroy(){
super.destroy();
if (this.snapper) {
this.snapper.destroy();
}
}
}
export default ContinuousViewManager;

View file

@ -0,0 +1,309 @@
import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
import { EVENTS, DOM_EVENTS } from "../../utils/constants";
import EventEmitter from "event-emitter";
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
const PI_D2 = (Math.PI / 2);
const EASING_EQUATIONS = {
easeOutSine: function (pos) {
return Math.sin(pos * PI_D2);
},
easeInOutSine: function (pos) {
return (-0.5 * (Math.cos(Math.PI * pos) - 1));
},
easeInOutQuint: function (pos) {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 5);
}
return 0.5 * (Math.pow((pos - 2), 5) + 2);
},
easeInCubic: function(pos) {
return Math.pow(pos, 3);
}
};
class Snap {
constructor(manager, options) {
if (this.supportsTouch() === false) {
return;
}
this.settings = extend({
duration: 80,
minVelocity: 0.2,
minDistance: 10,
easing: EASING_EQUATIONS['easeInCubic']
}, options || {});
this.setup(manager);
}
setup(manager) {
this.manager = manager;
this.layout = this.manager.layout;
this.fullsize = this.manager.settings.fullsize;
if (this.fullsize) {
this.element = this.manager.stage.element;
this.scroller = window;
this.disableScroll();
} else {
this.element = this.manager.stage.container;
this.scroller = this.element;
this.element.style["WebkitOverflowScrolling"] = "touch";
}
this.overflow = this.manager.overflow;
// set lookahead offset to page width
this.manager.settings.offset = this.layout.width;
this.manager.settings.afterScrolledTimeout = this.settings.duration * 2;
this.isVertical = this.manager.settings.axis === "vertical";
// disable snapping if not paginated or axis in not horizontal
if (!this.manager.isPaginated || this.isVertical) {
return;
}
this.touchCanceler = false;
this.resizeCanceler = false;
this.snapping = false;
this.scrollLeft;
this.scrollTop;
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
this.addListeners();
}
supportsTouch() {
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
return true;
}
return false;
}
disableScroll() {
this.element.style.overflow = "hidden";
}
enableScroll() {
this.element.style.overflow = "scroll";
}
addListeners() {
window.addEventListener('resize', this.onResize.bind(this));
this.scroller.addEventListener('scroll', this.onScroll.bind(this));
window.addEventListener('touchstart', this.onTouchStart.bind(this), { passive: true });
this.on('touchstart', this.onTouchStart.bind(this));
window.addEventListener('touchmove', this.onTouchMove.bind(this), { passive: true });
this.on('touchmove', this.onTouchMove.bind(this));
window.addEventListener('touchend', this.onTouchEnd.bind(this), { passive: true });
this.on('touchend', this.onTouchEnd.bind(this));
this.manager.on(EVENTS.MANAGERS.ADDED, this.afterDisplayed.bind(this));
}
afterDisplayed(view) {
let contents = view.contents;
["touchstart", "touchmove", "touchend"].forEach((e) => {
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
});
}
triggerViewEvent(e, contents){
this.emit(e.type, e, contents);
}
onScroll(e) {
this.scrollLeft = this.fullsize ? window.scrollX : this.scroller.scrollLeft;
this.scrollTop = this.fullsize ? window.scrollY : this.scroller.scrollTop;
}
onResize(e) {
this.resizeCanceler = true;
}
onTouchStart(e) {
let { screenX, screenY } = e.touches[0];
if (this.fullsize) {
this.enableScroll();
}
this.touchCanceler = true;
if (!this.startTouchX) {
this.startTouchX = screenX;
this.startTouchY = screenY;
this.startTime = this.now();
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchMove(e) {
let { screenX, screenY } = e.touches[0];
let deltaY = Math.abs(screenY - this.endTouchY);
this.touchCanceler = true;
if (!this.fullsize && deltaY < 10) {
this.element.scrollLeft -= screenX - this.endTouchX;
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchEnd(e) {
if (this.fullsize) {
this.disableScroll();
}
this.touchCanceler = false;
let swipped = this.wasSwiped();
if (swipped !== 0) {
this.snap(swipped);
} else {
this.snap();
}
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
}
wasSwiped() {
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let distance = (this.endTouchX - this.startTouchX);
let absolute = Math.abs(distance);
let time = this.endTime - this.startTime;
let velocity = (distance / time);
let minVelocity = this.settings.minVelocity;
if (absolute <= this.settings.minDistance || absolute >= snapWidth) {
return 0;
}
if (velocity > minVelocity) {
// previous
return -1;
} else if (velocity < -minVelocity) {
// next
return 1;
}
}
needsSnap() {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
return (left % snapWidth) !== 0;
}
snap(howMany=0) {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let snapTo = Math.round(left / snapWidth) * snapWidth;
if (howMany) {
snapTo += (howMany * snapWidth);
}
return this.smoothScrollTo(snapTo);
}
smoothScrollTo(destination) {
const deferred = new defer();
const start = this.scrollLeft;
const startTime = this.now();
const duration = this.settings.duration;
const easing = this.settings.easing;
this.snapping = true;
// add animation loop
function tick() {
const now = this.now();
const time = Math.min(1, ((now - startTime) / duration));
const timeFunction = easing(time);
if (this.touchCanceler || this.resizeCanceler) {
this.resizeCanceler = false;
this.snapping = false;
deferred.resolve();
return;
}
if (time < 1) {
window.requestAnimationFrame(tick.bind(this));
this.scrollTo(start + ((destination - start) * time), 0);
} else {
this.scrollTo(destination, 0);
this.snapping = false;
deferred.resolve();
}
}
tick.call(this);
return deferred.promise;
}
scrollTo(left=0, top=0) {
if (this.fullsize) {
window.scroll(left, top);
} else {
this.scroller.scrollLeft = left;
this.scroller.scrollTop = top;
}
}
now() {
return ('now' in window.performance) ? performance.now() : new Date().getTime();
}
destroy() {
this.scroller.removeEventListener('scroll', this.onScroll.bind(this));
window.removeEventListener('resize', this.onResize.bind(this));
window.removeEventListener('touchstart', this.onTouchStart.bind(this), { passive: true });
window.removeEventListener('touchmove', this.onTouchMove.bind(this), { passive: true });
window.removeEventListener('touchend', this.onTouchEnd.bind(this), { passive: true });
}
}
EventEmitter(Snap.prototype);
export default Snap;

View file

@ -8,7 +8,7 @@ import Layout from "./layout";
import Themes from "./themes";
import Contents from "./contents";
import Annotations from "./annotations";
import { EVENTS } from "./utils/constants";
import { EVENTS, DOM_EVENTS } from "./utils/constants";
// Default Views
import IframeView from "./managers/views/iframe";
@ -35,6 +35,7 @@ import ContinuousViewManager from "./managers/continuous/index";
* @param {string} [options.stylesheet] url of stylesheet to be injected
* @param {boolean} [options.resizeOnOrientationChange] false to disable orientation events
* @param {string} [options.script] url of script to be injected
* @param {boolean | object} [options.snap=false] use snap scrolling
*/
class Rendition {
constructor(book, options) {
@ -51,7 +52,8 @@ class Rendition {
minSpreadWidth: 800,
stylesheet: null,
resizeOnOrientationChange: true,
script: null
script: null,
snap: false
});
extend(this.settings, options);
@ -852,9 +854,7 @@ class Rendition {
* @param {Contents} view contents
*/
passEvents(contents){
var listenedEvents = Contents.listenedEvents;
listenedEvents.forEach((e) => {
DOM_EVENTS.forEach((e) => {
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
});

View file

@ -1,7 +1,7 @@
export const EPUBJS_VERSION = "0.3";
// Dom events to listen for
export const DOM_EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
export const DOM_EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart", "touchmove"];
export const EVENTS = {
BOOK : {

View file

@ -23,6 +23,7 @@ export interface RenditionOptions {
script?: string,
infinite?: boolean,
overflow?: string,
snap?: boolean | object,
}
export interface DisplayedLocation {