Skip to content

Instantly share code, notes, and snippets.

@leejunhui
Last active March 11, 2023 13:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leejunhui/8b4f6d149798eb98bf765bcc324f20f2 to your computer and use it in GitHub Desktop.
Save leejunhui/8b4f6d149798eb98bf765bcc324f20f2 to your computer and use it in GitHub Desktop.
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';
class ContinuousViewManager extends DefaultViewManager {
constructor(options) {
super(options);
this.name = 'continuous';
this.settings = extend(this.settings || {}, {
infinite: true,
overflow: undefined,
axis: undefined,
writingMode: undefined,
flow: 'scrolled',
offset: 500,
offsetDelta: 250,
width: undefined,
height: undefined,
snap: false,
afterScrolledTimeout: options.afterScrolledTimeout,
allowScriptedContent: false,
allowPopups: false,
capableScrollPage: undefined,
});
extend(this.settings, options.settings || {});
// Gap can be 0, but defaults doesn't handle that
if (options.settings.gap != 'undefined' && options.settings.gap === 0) {
this.settings.gap = options.settings.gap;
}
this.viewSettings = {
ignoreClass: this.settings.ignoreClass,
axis: this.settings.axis,
flow: this.settings.flow,
layout: this.layout,
width: 0,
height: 0,
forceEvenPages: false,
allowScriptedContent: this.settings.allowScriptedContent,
allowPopups: this.settings.allowPopups,
};
this.scrollTop = 0;
this.scrollLeft = 0;
this.waitForTrimViewList = [];
}
display(section, target) {
return DefaultViewManager.prototype.display
.call(this, section, target)
.then(
function () {
return this.fill();
}.bind(this)
);
}
fill(_full) {
var full = _full || new defer();
this.q
.enqueue(() => {
return this.check();
})
.then((result) => {
if (result) {
this.fill(full);
} else {
full.resolve();
}
});
return full.promise;
}
moveTo(offset) {
// var bounds = this.stage.bounds();
// var dist = Math.floor(offset.top / bounds.height) * bounds.height;
var distX = 0,
distY = 0;
var offsetX = 0,
offsetY = 0;
if (!this.isPaginated) {
distY = offset.top;
offsetY = offset.top + this.settings.offsetDelta;
} else {
distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
offsetX = distX + this.settings.offsetDelta;
}
if (distX > 0 || distY > 0) {
this.scrollBy(distX, distY, true);
}
}
afterResized(view) {
this.emit(EVENTS.MANAGERS.RESIZE, view.section);
}
// Remove Previous Listeners if present
removeShownListeners(view) {
// view.off("shown", this.afterDisplayed);
// view.off("shown", this.afterDisplayedAbove);
view.onDisplayed = function () {};
}
add(section) {
var view = this.createView(section);
this.views.append(view);
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
view.expanded = true;
});
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
// view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
view.onDisplayed = this.afterDisplayed.bind(this);
view.onResize = this.afterResized.bind(this);
return view.display(this.request);
}
append(section) {
if (this.snapper.snapping && this.scrollLeft !== this.snapper.snapTarget) {
// console.log(
// 'AAA - append 被过滤了',
// section,
// this.scrollLeft,
// this.snapper.snapTarget
// );
// return;
}
// console.log('AAA - append 没有被过滤', section);
var view = this.createView(section);
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
view.expanded = true;
});
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
this.views.append(view);
view.onDisplayed = this.afterDisplayed.bind(this);
return view;
}
prepend(section) {
// if (this.snapper.snapping && this.scrollLeft !== this.snapper.snapTarget) {
// console.log(
// 'AAA - prepend 被过滤了',
// section,
// this.scrollLeft,
// this.snapper.snapTarget,
// this.snapper.snapTo
// );
// return;
// }
// console.log('AAA - prepend 没有被过滤', section);
var view = this.createView(section);
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
// console.log('EVENTS.VIEWS.RESIZED 来了', view.id, bounds);
this.counter(bounds);
view.expanded = true;
});
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
this.views.prepend(view);
view.onDisplayed = this.afterDisplayed.bind(this);
return view;
}
counter(bounds) {
if (this.settings.axis === 'vertical') {
this.scrollBy(0, bounds.heightDelta, true);
} else {
// !!! 非常关键,确保 snapper 在 snapping 过程中不会再有额外的 scrollBy 影响 container 的偏移
// if (
// this.snapper.snapping &&
// this.scrollLeft !== this.snapper.snapTarget
// ) {
// console.log(
// 'epubjs - continious - counter - 被过滤掉了',
// this.snapper.snapping,
// this.scrollLeft
// );
// return;
// }
// console.log(
// 'epubjs - continious - counter - 没有过滤掉',
// this.snapper.snapping,
// this.scrollLeft
// );
this.scrollBy(bounds.widthDelta, 0, true);
}
}
trimWaitingView() {
setTimeout(() => {
while (this.waitForTrimViewList.length > 0) {
const view = this.waitForTrimViewList.shift();
this.q.enqueue(view.destroy.bind(view));
clearTimeout(this.trimTimeout);
this.trimTimeout = setTimeout(
function () {
this.q.enqueue(this.trim.bind(this));
}.bind(this),
250
);
}
}, 500);
}
update(_offset) {
// console.log('epubjs - continuious - update 来了');
var container = this.bounds();
var views = this.views.all();
var viewsLength = views.length;
var visible = [];
var offset =
typeof _offset != 'undefined' ? _offset : this.settings.offset || 0;
var isVisible;
var view;
var updating = new defer();
var promises = [];
for (var i = 0; i < viewsLength; i++) {
view = views[i];
isVisible = this.isVisible(view, offset, offset, container);
// console.log(
// `epubjs - continious - update - isVisible = ${isVisible}, view=${view.id}, offset = ${offset}`
// );
if (isVisible === true) {
// console.log("visible " + view.index, view.displayed);
if (!view.displayed) {
let displayed = view.display(this.request).then(
function (view) {
view.show();
},
(err) => {
view.hide();
}
);
promises.push(displayed);
} else {
view.show();
}
visible.push(view);
} else {
if (this.snapper.scrolling) {
// 正在滑动中,不执行 trim
// 将 view 加入队列中,在 snapper 滚动完成后执行对 view 的 trim 任务
// 先检查是否已经在队列中存在
const alreadyInList = this.waitForTrimViewList.some(
(v) => view.id === v.id
);
if (!alreadyInList) {
this.waitForTrimViewList.push(view);
}
console.log(
'epubjs - continuous - update - 因为仍然在 scrolling,所以 trim 加入队列',
this.waitForTrimViewList.map((v) => v.id)
);
return;
}
this.q.enqueue(view.destroy.bind(view));
clearTimeout(this.trimTimeout);
this.trimTimeout = setTimeout(
function () {
this.q.enqueue(this.trim.bind(this));
}.bind(this),
250
);
}
}
if (promises.length) {
return Promise.all(promises).catch((err) => {
updating.reject(err);
});
} else {
updating.resolve();
return updating.promise;
}
}
check(_offsetLeft, _offsetTop) {
// if (this.snapper.snapping) {
// console.log(
// 'check 被跳过了',
// _offsetLeft,
// this.snapper.snapping,
// this.snapper.snapTarget,
// this.snapper.snapTo
// );
// return;
// }
if (this.snapper.scrolling) {
return;
}
console.log(
`check 来了, this.scrollLeft=${this.scrollLeft}, this.views.length=${
this.views.length
}, this.snapper.snapping=${this.snapper.snapping}, this.snapper.snapTo=${
this.snapper.snapTo
}, this.snapper.snapTarget=${
this.snapper.snapTarget
}, Date.now()=${Date.now()}`
);
var checking = new defer();
var newViews = [];
var horizontal = this.settings.axis === 'horizontal';
var delta = this.settings.offset || 0;
if (_offsetLeft && horizontal) {
delta = _offsetLeft;
}
if (_offsetTop && !horizontal) {
delta = _offsetTop;
}
var bounds = this._bounds; // bounds saved this until resize
let offset = horizontal ? this.scrollLeft : this.scrollTop;
let visibleLength = horizontal ? Math.floor(bounds.width) : bounds.height;
let contentLength = horizontal
? this.container.scrollWidth
: this.container.scrollHeight;
let writingMode =
this.writingMode && this.writingMode.indexOf('vertical') === 0
? 'vertical'
: 'horizontal';
let rtlScrollType = this.settings.rtlScrollType;
let rtl = this.settings.direction === 'rtl';
if (!this.settings.fullsize) {
// Scroll offset starts at width of element
if (rtl && rtlScrollType === 'default' && writingMode === 'horizontal') {
offset = contentLength - visibleLength - offset;
}
// Scroll offset starts at 0 and goes negative
if (rtl && rtlScrollType === 'negative' && writingMode === 'horizontal') {
offset = offset * -1;
}
} else {
// Scroll offset starts at 0 and goes negative
if (
(horizontal && rtl && rtlScrollType === 'negative') ||
(!horizontal && rtl && rtlScrollType === 'default')
) {
offset = offset * -1;
}
}
let prepend = () => {
let first = this.views.first();
let prev = first && first.section.prev();
if (prev) {
const result = this.prepend(prev);
if (result) {
newViews.push(result);
}
}
};
let append = () => {
let last = this.views.last();
let next = last && last.section.next();
if (next) {
newViews.push(this.append(next));
}
};
let end = offset + visibleLength + delta;
let start = offset - delta;
if (end >= contentLength) {
append();
}
if (start < 0) {
console.log(
`start < 0 了,需要 prepend 了
this.settings.offset = ${this.settings.offset}
offset = ${offset}
delta = ${delta}
start = ${start}
this.views._views.length = ${this.views._views.length}
this.scrollLeft = ${this.scrollLeft}
`
);
// this.views.forEach(function (view) {
// if (view) {
// console.log('start < 0 了,需要 prepend 了 -- view', view.id);
// }
// });
prepend();
}
newViews = newViews.filter((view) => view != null);
// iframe display => render => expand
let promises = newViews.map((view) => {
if (view) {
return view.display(this.request);
} else {
console.log('wtf - view 居然为空');
return undefined;
}
// return view.display(this.request);
});
if (newViews.length) {
return Promise.all(promises)
.then(() => {
return this.check();
})
.then(
() => {
// Check to see if anything new is on screen after rendering
return this.update(delta);
},
(err) => {
return err;
}
);
} else {
this.q.enqueue(
function () {
this.update();
}.bind(this)
);
checking.resolve(false);
return checking.promise;
}
}
trim() {
var task = new defer();
var displayed = this.views.displayed();
var first = displayed[0];
var last = displayed[displayed.length - 1];
var firstIndex = this.views.indexOf(first);
var lastIndex = this.views.indexOf(last);
var above = this.views.slice(0, firstIndex);
var below = this.views.slice(lastIndex + 1);
// Erase all but last above
for (var i = 0; i < above.length - 1; i++) {
this.erase(above[i], above);
}
// Erase all except first below
for (var j = 1; j < below.length; j++) {
this.erase(below[j]);
}
task.resolve();
return task.promise;
}
erase(view, above) {
//Trim
var prevTop;
var prevLeft;
if (!this.settings.fullsize) {
prevTop = this.container.scrollTop;
prevLeft = this.container.scrollLeft;
} else {
prevTop = window.scrollY;
prevLeft = window.scrollX;
}
var bounds = view.bounds();
this.views.remove(view);
if (above) {
if (this.settings.axis === 'vertical') {
this.scrollTo(0, prevTop - bounds.height, true);
} else {
if (this.settings.direction === 'rtl') {
if (!this.settings.fullsize) {
this.scrollTo(prevLeft, 0, true);
} else {
this.scrollTo(prevLeft + Math.floor(bounds.width), 0, true);
}
} else {
const eraseScrollTo = prevLeft - Math.floor(bounds.width);
// console.log(
// 'epubjs - continious - eraseScrollTo',
// eraseScrollTo,
// view.id
// );
this.scrollTo(eraseScrollTo, 0, true);
}
}
}
}
addEventListeners(stage) {
window.addEventListener(
'unload',
function (e) {
this.ignore = true;
// this.scrollTo(0,0);
this.destroy();
}.bind(this)
);
this.addScrollListeners();
if (this.isPaginated && this.settings.snap) {
this.snapper = new Snap(this, {
capableScrollPage: this.settings.capableScrollPage,
});
}
}
addScrollListeners() {
var scroller;
this.tick = requestAnimationFrame;
let dir =
this.settings.direction === 'rtl' &&
this.settings.rtlScrollType === 'default'
? -1
: 1;
this.scrollDeltaVert = 0;
this.scrollDeltaHorz = 0;
if (!this.settings.fullsize) {
scroller = this.container;
this.scrollTop = this.container.scrollTop;
this.scrollLeft = this.container.scrollLeft;
} else {
scroller = window;
this.scrollTop = window.scrollY * dir;
this.scrollLeft = window.scrollX * dir;
}
this._onScroll = this.onScroll.bind(this);
scroller.addEventListener('scroll', this._onScroll);
this._scrolled = debounce(this.scrolled.bind(this), 30);
// this.tick.call(window, this.onScroll.bind(this));
this.didScroll = false;
}
removeEventListeners() {
var scroller;
if (!this.settings.fullsize) {
scroller = this.container;
} else {
scroller = window;
}
scroller.removeEventListener('scroll', this._onScroll);
this._onScroll = undefined;
}
onScroll() {
let scrollTop;
let scrollLeft;
let dir =
this.settings.direction === 'rtl' &&
this.settings.rtlScrollType === 'default'
? -1
: 1;
if (!this.settings.fullsize) {
scrollTop = this.container.scrollTop;
scrollLeft = this.container.scrollLeft;
} else {
scrollTop = window.scrollY * dir;
scrollLeft = window.scrollX * dir;
}
this.scrollTop = scrollTop;
this.scrollLeft = scrollLeft;
if (!this.ignore && this.prevScrollLeft !== this.scrollLeft) {
this._scrolled();
} else {
this.ignore = false;
}
this.scrollDeltaVert += Math.abs(scrollTop - this.prevScrollTop);
this.scrollDeltaHorz += Math.abs(scrollLeft - this.prevScrollLeft);
this.prevScrollTop = scrollTop;
this.prevScrollLeft = scrollLeft;
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(
function () {
this.scrollDeltaVert = 0;
this.scrollDeltaHorz = 0;
}.bind(this),
150
);
clearTimeout(this.afterScrolled);
this.didScroll = false;
}
scrolled() {
// console.log('epubjs - continuious - scrolled 来了', Date.now());
// TODO: cenfeng -
this.q.enqueue(
function () {
return this.check();
}.bind(this)
);
// console.log('epubjs - continuious - check 加入队列了');
this.emit(EVENTS.MANAGERS.SCROLL, {
top: this.scrollTop,
left: this.scrollLeft,
});
clearTimeout(this.afterScrolled);
this.afterScrolled = setTimeout(
function () {
// console.log('epubjs - continuious - afterScrolled 开始执行了');
// Don't report scroll if we are about the snap
if (
this.snapper &&
this.snapper.supportsTouch &&
this.snapper.needsSnap()
) {
// console.log('epubjs - continuious - snapper return 了');
return;
}
// console.log('epubjs - continuious - EVENTS.MANAGERS.SCROLLED 来了');
this.emit(EVENTS.MANAGERS.SCROLLED, {
top: this.scrollTop,
left: this.scrollLeft,
});
}.bind(this),
50
);
// console.log(
// 'epubjs - continuious - this.afterScrolled 被赋值了',
// this.afterScrolled
// );
}
next() {
let delta =
this.layout.props.name === 'pre-paginated' && this.layout.props.spread
? this.layout.props.delta * 2
: this.layout.props.delta;
if (!this.views.length) return;
if (this.isPaginated && this.settings.axis === 'horizontal') {
this.scrollBy(delta, 0, true);
} else {
this.scrollBy(0, this.layout.height, true);
}
this.q.enqueue(
function () {
return this.check();
}.bind(this)
);
}
prev() {
let delta =
this.layout.props.name === 'pre-paginated' && this.layout.props.spread
? this.layout.props.delta * 2
: this.layout.props.delta;
if (!this.views.length) return;
if (this.isPaginated && this.settings.axis === 'horizontal') {
this.scrollBy(-delta, 0, true);
} else {
this.scrollBy(0, -this.layout.height, true);
}
this.q.enqueue(
function () {
return this.check();
}.bind(this)
);
}
updateFlow(flow) {
if (this.rendered && this.snapper) {
this.snapper.destroy();
this.snapper = undefined;
}
super.updateFlow(flow, 'scroll');
if (this.rendered && this.isPaginated && this.settings.snap) {
this.snapper = new Snap(
this,
this.settings.snap &&
typeof this.settings.snap === 'object' &&
this.settings.snap
);
}
}
destroy() {
super.destroy();
if (this.snapper) {
this.snapper.destroy();
}
}
}
export default ContinuousViewManager;
import EventEmitter from 'event-emitter';
import { extend, defer, windowBounds, isNumber } from '../../utils/core';
import scrollType from '../../utils/scrolltype';
import Mapping from '../../mapping';
import Queue from '../../utils/queue';
import Stage from '../helpers/stage';
import Views from '../helpers/views';
import { EVENTS } from '../../utils/constants';
class DefaultViewManager {
constructor(options) {
this.name = 'default';
this.optsSettings = options.settings;
this.View = options.view;
this.request = options.request;
this.renditionQueue = options.queue;
this.q = new Queue(this);
this.settings = extend(this.settings || {}, {
infinite: true,
hidden: false,
width: undefined,
height: undefined,
axis: undefined,
writingMode: undefined,
flow: 'scrolled',
ignoreClass: '',
fullsize: undefined,
allowScriptedContent: false,
allowPopups: false,
baseUrl: undefined,
});
extend(this.settings, options.settings || {});
this.viewSettings = {
ignoreClass: this.settings.ignoreClass,
axis: this.settings.axis,
flow: this.settings.flow,
layout: this.layout,
method: this.settings.method, // srcdoc, blobUrl, write
width: 0,
height: 0,
forceEvenPages: true,
allowScriptedContent: this.settings.allowScriptedContent,
allowPopups: this.settings.allowPopups,
};
this.rendered = false;
}
render(element, size) {
let tag = element.tagName;
if (
typeof this.settings.fullsize === 'undefined' &&
tag &&
(tag.toLowerCase() == 'body' || tag.toLowerCase() == 'html')
) {
this.settings.fullsize = true;
}
if (this.settings.fullsize) {
this.settings.overflow = 'visible';
this.overflow = this.settings.overflow;
}
this.settings.size = size;
this.settings.rtlScrollType = scrollType();
// Save the stage
this.stage = new Stage({
width: size.width,
height: size.height,
overflow: this.overflow,
hidden: this.settings.hidden,
axis: this.settings.axis,
fullsize: this.settings.fullsize,
direction: this.settings.direction,
});
this.stage.attachTo(element);
// Get this stage container div
this.container = this.stage.getContainer();
// Views array methods
this.views = new Views(this.container);
// Calculate Stage Size
this._bounds = this.bounds();
this._stageSize = this.stage.size();
// Set the dimensions for views
this.viewSettings.width = this._stageSize.width;
this.viewSettings.height = this._stageSize.height;
// Function to handle a resize event.
// Will only attach if width and height are both fixed.
this.stage.onResize(this.onResized.bind(this));
this.stage.onOrientationChange(this.onOrientationChange.bind(this));
// Add Event Listeners
this.addEventListeners();
// Add Layout method
// this.applyLayoutMethod();
if (this.layout) {
this.updateLayout();
}
this.rendered = true;
}
addEventListeners() {
var scroller;
window.addEventListener(
'unload',
function (e) {
this.destroy();
}.bind(this)
);
if (!this.settings.fullsize) {
scroller = this.container;
} else {
scroller = window;
}
this._onScroll = this.onScroll.bind(this);
scroller.addEventListener('scroll', this._onScroll);
}
removeEventListeners() {
var scroller;
if (!this.settings.fullsize) {
scroller = this.container;
} else {
scroller = window;
}
scroller.removeEventListener('scroll', this._onScroll);
this._onScroll = undefined;
}
destroy() {
clearTimeout(this.orientationTimeout);
clearTimeout(this.resizeTimeout);
clearTimeout(this.afterScrolled);
this.clear();
this.removeEventListeners();
this.stage.destroy();
this.rendered = false;
/*
clearTimeout(this.trimTimeout);
if(this.settings.hidden) {
this.element.removeChild(this.wrapper);
} else {
this.element.removeChild(this.container);
}
*/
}
onOrientationChange(e) {
let { orientation } = window;
if (this.optsSettings.resizeOnOrientationChange) {
this.resize();
}
// Per ampproject:
// In IOS 10.3, the measured size of an element is incorrect if the
// element size depends on window size directly and the measurement
// happens in window.resize event. Adding a timeout for correct
// measurement. See https://github.com/ampproject/amphtml/issues/8479
clearTimeout(this.orientationTimeout);
this.orientationTimeout = setTimeout(
function () {
this.orientationTimeout = undefined;
if (this.optsSettings.resizeOnOrientationChange) {
this.resize();
}
this.emit(EVENTS.MANAGERS.ORIENTATION_CHANGE, orientation);
}.bind(this),
500
);
}
onResized(e) {
this.resize();
}
resize(width, height, epubcfi) {
let stageSize = this.stage.size(width, height);
// For Safari, wait for orientation to catch up
// if the window is a square
this.winBounds = windowBounds();
if (
this.orientationTimeout &&
this.winBounds.width === this.winBounds.height
) {
// reset the stage size for next resize
this._stageSize = undefined;
return;
}
if (
this._stageSize &&
this._stageSize.width === stageSize.width &&
this._stageSize.height === stageSize.height
) {
// Size is the same, no need to resize
return;
}
this._stageSize = stageSize;
this._bounds = this.bounds();
// Clear current views
this.clear();
// Update for new views
this.viewSettings.width = this._stageSize.width;
this.viewSettings.height = this._stageSize.height;
this.updateLayout();
this.emit(
EVENTS.MANAGERS.RESIZED,
{
width: this._stageSize.width,
height: this._stageSize.height,
},
epubcfi
);
}
createView(section, forceRight) {
return new this.View(section, extend(this.viewSettings, { forceRight }));
}
handleNextPrePaginated(forceRight, section, action) {
let next;
if (this.layout.name === 'pre-paginated' && this.layout.divisor > 1) {
if (forceRight || section.index === 0) {
// First page (cover) should stand alone for pre-paginated books
return;
}
next = section.next();
if (next && !next.properties.includes('page-spread-left')) {
return action.call(this, next);
}
}
}
display(section, target) {
var displaying = new defer();
var displayed = displaying.promise;
// Check if moving to target is needed
if (target === section.href || isNumber(target)) {
target = undefined;
}
// Check to make sure the section we want isn't already shown
var visible = this.views.find(section);
// View is already shown, just move to correct location in view
if (visible && section && this.layout.name !== 'pre-paginated') {
let offset = visible.offset();
if (this.settings.direction === 'ltr') {
this.scrollTo(offset.left, offset.top, true);
} else {
let width = visible.width();
this.scrollTo(offset.left + width, offset.top, true);
}
if (target) {
let offset = visible.locationOf(target);
let width = visible.width();
this.moveTo(offset, width);
}
displaying.resolve();
return displayed;
}
// Hide all current views
this.clear();
let forceRight = false;
if (
this.layout.name === 'pre-paginated' &&
this.layout.divisor === 2 &&
section.properties.includes('page-spread-right')
) {
forceRight = true;
}
this.add(section, forceRight)
.then(
function (view) {
// Move to correct place within the section, if needed
if (target) {
let offset = view.locationOf(target);
let width = view.width();
this.moveTo(offset, width);
}
}.bind(this),
(err) => {
displaying.reject(err);
}
)
.then(
function () {
return this.handleNextPrePaginated(forceRight, section, this.add);
}.bind(this)
)
.then(
function () {
this.views.show();
displaying.resolve();
}.bind(this)
);
// .then(function(){
// return this.hooks.display.trigger(view);
// }.bind(this))
// .then(function(){
// this.views.show();
// }.bind(this));
return displayed;
}
afterDisplayed(view) {
this.emit(EVENTS.MANAGERS.ADDED, view);
}
afterResized(view) {
this.emit(EVENTS.MANAGERS.RESIZE, view.section);
}
moveTo(offset, width) {
var distX = 0,
distY = 0;
if (!this.isPaginated) {
distY = offset.top;
} else {
distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
if (distX + this.layout.delta > this.container.scrollWidth) {
distX = this.container.scrollWidth - this.layout.delta;
}
distY = Math.floor(offset.top / this.layout.delta) * this.layout.delta;
if (distY + this.layout.delta > this.container.scrollHeight) {
distY = this.container.scrollHeight - this.layout.delta;
}
}
if (this.settings.direction === 'rtl') {
/***
the `floor` function above (L343) is on positive values, so we should add one `layout.delta`
to distX or use `Math.ceil` function, or multiply offset.left by -1
before `Math.floor`
*/
distX = distX + this.layout.delta;
distX = distX - width;
}
this.scrollTo(distX, distY, true);
}
add(section, forceRight) {
try {
var view = this.createView(section, forceRight);
this.views.append(view);
// view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
view.onDisplayed = this.afterDisplayed.bind(this);
view.onResize = this.afterResized.bind(this);
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
// console.log('epubjs - defaultManager - 418');
return view.display(this.request);
} catch (error) {
// console.log('epubjs - defaultManager - 421', error);
}
}
append(section, forceRight) {
var view = this.createView(section, forceRight);
this.views.append(view);
view.onDisplayed = this.afterDisplayed.bind(this);
view.onResize = this.afterResized.bind(this);
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
return view.display(this.request);
}
prepend(section, forceRight) {
var view = this.createView(section, forceRight);
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
this.counter(bounds);
});
this.views.prepend(view);
view.onDisplayed = this.afterDisplayed.bind(this);
view.onResize = this.afterResized.bind(this);
view.on(EVENTS.VIEWS.AXIS, (axis) => {
this.updateAxis(axis);
});
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
this.updateWritingMode(mode);
});
return view.display(this.request);
}
counter(bounds) {
if (this.settings.axis === 'vertical') {
this.scrollBy(0, bounds.heightDelta, true);
} else {
this.scrollBy(bounds.widthDelta, 0, true);
}
}
// resizeView(view) {
//
// if(this.settings.globalLayoutProperties.layout === "pre-paginated") {
// view.lock("both", this.bounds.width, this.bounds.height);
// } else {
// view.lock("width", this.bounds.width, this.bounds.height);
// }
//
// };
next() {
var next;
var left;
let dir = this.settings.direction;
if (!this.views.length) return;
if (
this.isPaginated &&
this.settings.axis === 'horizontal' &&
(!dir || dir === 'ltr')
) {
this.scrollLeft = this.container.scrollLeft;
left =
this.container.scrollLeft +
this.container.offsetWidth +
this.layout.delta;
// 之所以要去掉等于的时候 scrollBy,是因为在 epub.js 中插入 easy-marker 的节点后,会导致每一章后面都会多一个空白页
if (left <= this.container.scrollWidth) {
this.scrollBy(this.layout.delta, 0, true);
} else {
next = this.views.last().section.next();
}
} else if (
this.isPaginated &&
this.settings.axis === 'horizontal' &&
dir === 'rtl'
) {
this.scrollLeft = this.container.scrollLeft;
if (this.settings.rtlScrollType === 'default') {
left = this.container.scrollLeft;
if (left > 0) {
this.scrollBy(this.layout.delta, 0, true);
} else {
next = this.views.last().section.next();
}
} else {
left = this.container.scrollLeft + this.layout.delta * -1;
if (left > this.container.scrollWidth * -1) {
this.scrollBy(this.layout.delta, 0, true);
} else {
next = this.views.last().section.next();
}
}
} else if (this.isPaginated && this.settings.axis === 'vertical') {
this.scrollTop = this.container.scrollTop;
let top = this.container.scrollTop + this.container.offsetHeight;
if (top < this.container.scrollHeight) {
this.scrollBy(0, this.layout.height, true);
} else {
next = this.views.last().section.next();
}
} else {
next = this.views.last().section.next();
}
if (next) {
if (this.settings.baseUrl && !next.baseUrl) {
next.baseUrl = this.settings.baseUrl;
}
this.clear();
// The new section may have a different writing-mode from the old section. Thus, we need to update layout.
this.updateLayout();
let forceRight = false;
if (
this.layout.name === 'pre-paginated' &&
this.layout.divisor === 2 &&
next.properties.includes('page-spread-right')
) {
forceRight = true;
}
return this.append(next, forceRight)
.then(
function () {
return this.handleNextPrePaginated(forceRight, next, this.append);
}.bind(this),
(err) => {
return err;
}
)
.then(
function () {
// Reset position to start for scrolled-doc vertical-rl in default mode
if (
!this.isPaginated &&
this.settings.axis === 'horizontal' &&
this.settings.direction === 'rtl' &&
this.settings.rtlScrollType === 'default'
) {
this.scrollTo(this.container.scrollWidth, 0, true);
}
this.views.show();
}.bind(this)
);
}
}
prev() {
var prev;
var left;
let dir = this.settings.direction;
if (!this.views.length) return;
if (
this.isPaginated &&
this.settings.axis === 'horizontal' &&
(!dir || dir === 'ltr')
) {
this.scrollLeft = this.container.scrollLeft;
left = this.container.scrollLeft;
if (left > 0) {
this.scrollBy(-this.layout.delta, 0, true);
} else {
prev = this.views.first().section.prev();
}
} else if (
this.isPaginated &&
this.settings.axis === 'horizontal' &&
dir === 'rtl'
) {
this.scrollLeft = this.container.scrollLeft;
if (this.settings.rtlScrollType === 'default') {
left = this.container.scrollLeft + this.container.offsetWidth;
if (left < this.container.scrollWidth) {
this.scrollBy(-this.layout.delta, 0, true);
} else {
prev = this.views.first().section.prev();
}
} else {
left = this.container.scrollLeft;
if (left < 0) {
this.scrollBy(-this.layout.delta, 0, true);
} else {
prev = this.views.first().section.prev();
}
}
} else if (this.isPaginated && this.settings.axis === 'vertical') {
this.scrollTop = this.container.scrollTop;
let top = this.container.scrollTop;
if (top > 0) {
this.scrollBy(0, -this.layout.height, true);
} else {
prev = this.views.first().section.prev();
}
} else {
prev = this.views.first().section.prev();
}
if (prev) {
this.clear();
// The new section may have a different writing-mode from the old section. Thus, we need to update layout.
this.updateLayout();
let forceRight = false;
if (
this.layout.name === 'pre-paginated' &&
this.layout.divisor === 2 &&
typeof prev.prev() !== 'object'
) {
forceRight = true;
}
return this.prepend(prev, forceRight)
.then(
function () {
var left;
if (
this.layout.name === 'pre-paginated' &&
this.layout.divisor > 1
) {
left = prev.prev();
if (left) {
return this.prepend(left);
}
}
}.bind(this),
(err) => {
return err;
}
)
.then(
function () {
if (this.isPaginated && this.settings.axis === 'horizontal') {
if (this.settings.direction === 'rtl') {
if (this.settings.rtlScrollType === 'default') {
this.scrollTo(0, 0, true);
} else {
this.scrollTo(
this.container.scrollWidth * -1 + this.layout.delta,
0,
true
);
}
} else {
// 之所以要减去 layout.delta * 2,是因为在 epub.js 中插入 easy-marker 的节点后,会导致每一章后面都会多一个空白页
this.scrollTo(
this.container.scrollWidth - this.layout.delta,
0,
true
);
}
}
this.views.show();
}.bind(this)
);
}
}
current() {
var visible = this.visible();
if (visible.length) {
// Current is the last visible view
return visible[visible.length - 1];
}
return null;
}
clear() {
// this.q.clear();
if (this.views) {
this.views.hide();
this.scrollTo(0, 0, true);
this.views.clear();
}
}
currentLocation() {
this.updateLayout();
if (this.isPaginated && this.settings.axis === 'horizontal') {
this.location = this.paginatedLocation();
} else {
this.location = this.scrolledLocation();
}
return this.location;
}
scrolledLocation() {
let visible = this.visible();
let container = this.container.getBoundingClientRect();
let pageHeight =
container.height < window.innerHeight
? container.height
: window.innerHeight;
let pageWidth =
container.width < window.innerWidth ? container.width : window.innerWidth;
let vertical = this.settings.axis === 'vertical';
let rtl = this.settings.direction === 'rtl';
let offset = 0;
let used = 0;
if (this.settings.fullsize) {
offset = vertical ? window.scrollY : window.scrollX;
}
let sections = visible.map((view) => {
let { index, href } = view.section;
let position = view.position();
let width = view.width();
let height = view.height();
let startPos;
let endPos;
let stopPos;
let totalPages;
if (vertical) {
startPos = offset + container.top - position.top + used;
endPos = startPos + pageHeight - used;
totalPages = this.layout.count(height, pageHeight).pages;
stopPos = pageHeight;
} else {
startPos = offset + container.left - position.left + used;
endPos = startPos + pageWidth - used;
totalPages = this.layout.count(width, pageWidth).pages;
stopPos = pageWidth;
}
let currPage = Math.ceil(startPos / stopPos);
let pages = [];
let endPage = Math.ceil(endPos / stopPos);
// Reverse page counts for horizontal rtl
if (this.settings.direction === 'rtl' && !vertical) {
let tempStartPage = currPage;
currPage = totalPages - endPage;
endPage = totalPages - tempStartPage;
}
pages = [];
for (var i = currPage; i <= endPage; i++) {
let pg = i + 1;
pages.push(pg);
}
let mapping = this.mapping.page(
view.contents,
view.section.cfiBase,
startPos,
endPos
);
return {
index,
href,
pages,
totalPages,
mapping,
};
});
return sections;
}
paginatedLocation() {
let visible = this.visible();
let container = this.container.getBoundingClientRect();
let left = 0;
let used = 0;
if (this.settings.fullsize) {
left = window.scrollX;
}
let sections = visible.map((view) => {
let { index, href } = view.section;
let offset;
let position = view.position();
let width = view.width();
// Find mapping
let start;
let end;
let pageWidth;
if (this.settings.direction === 'rtl') {
offset = container.right - left;
pageWidth =
Math.min(Math.abs(offset - position.left), this.layout.width) - used;
end = position.width - (position.right - offset) - used;
start = end - pageWidth;
} else {
offset = container.left + left;
pageWidth = Math.min(position.right - offset, this.layout.width) - used;
start = offset - position.left + used;
end = start + pageWidth;
}
used += pageWidth;
let mapping = this.mapping.page(
view.contents,
view.section.cfiBase,
start,
end
);
let totalPages = this.layout.count(width).pages;
let startPage = Math.floor(start / this.layout.pageWidth);
let pages = [];
let endPage = Math.floor(end / this.layout.pageWidth);
// start page should not be negative
if (startPage < 0) {
startPage = 0;
endPage = endPage + 1;
}
// Reverse page counts for rtl
if (this.settings.direction === 'rtl') {
let tempStartPage = startPage;
startPage = totalPages - endPage;
endPage = totalPages - tempStartPage;
}
for (var i = startPage + 1; i <= endPage; i++) {
let pg = i;
pages.push(pg);
}
return {
index,
href,
pages,
totalPages,
mapping,
};
});
return sections;
}
isVisible(view, offsetPrev, offsetNext, _container) {
var position = view.position();
var container = _container || this.bounds();
const isVisibleResult =
position.right > container.left - offsetPrev &&
position.left < container.right + offsetNext;
// console.log(
// `=== isVisible isVisibleResult=${isVisibleResult} view=${
// view.id
// } offsetPrev=${offsetPrev} offsetNext=${offsetNext} position=${JSON.stringify(
// position
// )} container=${JSON.stringify(container)}`
// );
if (
this.settings.axis === 'horizontal' &&
position.right > container.left - offsetPrev &&
position.left < container.right + offsetNext
) {
return true;
} else if (
this.settings.axis === 'vertical' &&
position.bottom > container.top - offsetPrev &&
position.top < container.bottom + offsetNext
) {
return true;
}
return false;
}
visible() {
var container = this.bounds();
var views = this.views.displayed();
var viewsLength = views.length;
var visible = [];
var isVisible;
var view;
for (var i = 0; i < viewsLength; i++) {
view = views[i];
isVisible = this.isVisible(view, 0, 0, container);
if (isVisible === true) {
visible.push(view);
}
}
return visible;
}
scrollBy(x, y, silent) {
let dir = this.settings.direction === 'rtl' ? -1 : 1;
// console.log(
// 'xxx epubjs - scrollBy - manager',
// x,
// Date.now(),
// this.snapper.swipped,
// this.container.scrollLeft
// );
// console.trace();
if (silent) {
this.ignore = true;
}
if (!this.settings.fullsize) {
if (x) {
this.container.scrollLeft += x * dir;
// console.log(
// 'xxx epubjs - scrollBy - manager - after',
// x,
// Date.now(),
// this.snapper.swipped,
// this.container.scrollLeft
// );
}
if (y) this.container.scrollTop += y;
} else {
window.scrollBy(x * dir, y * dir);
}
this.scrolled = true;
}
scrollTo(x, y, silent) {
if (silent) {
this.ignore = true;
}
// console.log('xxx epubjs - scrollTo - manager', x);
// console.trace();
if (!this.settings.fullsize) {
this.container.scrollLeft = x;
this.container.scrollTop = y;
} else {
window.scrollTo(x, y);
}
this.scrolled = true;
}
onScroll() {
let scrollTop;
let scrollLeft;
if (!this.settings.fullsize) {
scrollTop = this.container.scrollTop;
scrollLeft = this.container.scrollLeft;
} else {
scrollTop = window.scrollY;
scrollLeft = window.scrollX;
}
this.scrollTop = scrollTop;
this.scrollLeft = scrollLeft;
if (!this.ignore) {
this.emit(EVENTS.MANAGERS.SCROLL, {
top: scrollTop,
left: scrollLeft,
});
clearTimeout(this.afterScrolled);
this.afterScrolled = setTimeout(
function () {
this.emit(EVENTS.MANAGERS.SCROLLED, {
top: this.scrollTop,
left: this.scrollLeft,
});
}.bind(this),
20
);
} else {
this.ignore = false;
}
}
bounds() {
var bounds;
bounds = this.stage.bounds();
return bounds;
}
applyLayout(layout) {
this.layout = layout;
this.updateLayout();
if (
this.views &&
this.views.length > 0 &&
this.layout.name === 'pre-paginated'
) {
this.display(this.views.first().section);
}
// this.manager.layout(this.layout.format);
}
updateLayout() {
if (!this.stage) {
return;
}
this._stageSize = this.stage.size();
if (!this.isPaginated) {
this.layout.calculate(this._stageSize.width, this._stageSize.height);
} else {
this.layout.calculate(
this._stageSize.width,
this._stageSize.height,
this.settings.gap
);
// Set the look ahead offset for what is visible
this.settings.offset = this.layout.delta / this.layout.divisor;
// this.stage.addStyleRules("iframe", [{"margin-right" : this.layout.gap + "px"}]);
}
// Set the dimensions for views
this.viewSettings.width = this.layout.width;
this.viewSettings.height = this.layout.height;
this.setLayout(this.layout);
}
setLayout(layout) {
this.viewSettings.layout = layout;
this.mapping = new Mapping(
layout.props,
this.settings.direction,
this.settings.axis
);
if (this.views) {
this.views.forEach(function (view) {
if (view) {
view.setLayout(layout);
}
});
}
}
updateWritingMode(mode) {
this.writingMode = mode;
}
updateAxis(axis, forceUpdate) {
if (!forceUpdate && axis === this.settings.axis) {
return;
}
this.settings.axis = axis;
this.stage && this.stage.axis(axis);
this.viewSettings.axis = axis;
if (this.mapping) {
this.mapping = new Mapping(
this.layout.props,
this.settings.direction,
this.settings.axis
);
}
if (this.layout) {
if (axis === 'vertical') {
this.layout.spread('none');
} else {
this.layout.spread(this.layout.settings.spread);
}
}
}
updateFlow(flow, defaultScrolledOverflow = 'auto') {
let isPaginated = flow === 'paginated' || flow === 'auto';
this.isPaginated = isPaginated;
if (
flow === 'scrolled-doc' ||
flow === 'scrolled-continuous' ||
flow === 'scrolled'
) {
this.updateAxis('vertical');
} else {
this.updateAxis('horizontal');
}
this.viewSettings.flow = flow;
if (!this.settings.overflow) {
this.overflow = isPaginated ? 'hidden' : defaultScrolledOverflow;
} else {
this.overflow = this.settings.overflow;
}
this.stage && this.stage.overflow(this.overflow);
this.updateLayout();
}
getContents() {
var contents = [];
if (!this.views) {
return contents;
}
this.views.forEach(function (view) {
const viewContents = view && view.contents;
if (viewContents) {
contents.push(viewContents);
}
});
return contents;
}
direction(dir = 'ltr') {
this.settings.direction = dir;
this.stage && this.stage.direction(dir);
this.viewSettings.direction = dir;
this.updateLayout();
}
isRendered() {
return this.rendered;
}
}
//-- Enable binding events to Manager
EventEmitter(DefaultViewManager.prototype);
export default DefaultViewManager;
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) {
this.settings = extend(
{
duration: 160,
minVelocity: 0.2,
minDistance: 10,
easing: EASING_EQUATIONS['easeInCubic'],
},
options || {}
);
this.supportsTouch = this.supportsTouch();
if (this.supportsTouch) {
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.scrolling = false;
this.scrollLeft;
this.scrollTop;
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
this.swipped = false;
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 = '';
}
addListeners() {
this._onResize = this.onResize.bind(this);
window.addEventListener('resize', this._onResize);
this._onScroll = this.onScroll.bind(this);
this.scroller.addEventListener('scroll', this._onScroll);
this._onTouchStart = this.onTouchStart.bind(this);
this.scroller.addEventListener('touchstart', this._onTouchStart, {
passive: true,
});
this.on('touchstart', this._onTouchStart);
this._onTouchMove = this.onTouchMove.bind(this);
this.scroller.addEventListener('touchmove', this._onTouchMove, {
passive: true,
});
this.on('touchmove', this._onTouchMove);
this._onTouchEnd = this.onTouchEnd.bind(this);
this.scroller.addEventListener('touchend', this._onTouchEnd, {
passive: true,
});
this.on('touchend', this._onTouchEnd);
this._afterDisplayed = this.afterDisplayed.bind(this);
this.manager.on(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
}
removeListeners() {
window.removeEventListener('resize', this._onResize);
this._onResize = undefined;
this.scroller.removeEventListener('scroll', this._onScroll);
this._onScroll = undefined;
this.scroller.removeEventListener('touchstart', this._onTouchStart, {
passive: true,
});
this.off('touchstart', this._onTouchStart);
this._onTouchStart = undefined;
this.scroller.removeEventListener('touchmove', this._onTouchMove, {
passive: true,
});
this.off('touchmove', this._onTouchMove);
this._onTouchMove = undefined;
this.scroller.removeEventListener('touchend', this._onTouchEnd, {
passive: true,
});
this.off('touchend', this._onTouchEnd);
this._onTouchEnd = undefined;
this.manager.off(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
this._afterDisplayed = undefined;
}
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;
this.scrolling = 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.settings.capableScrollPage) {
const capableScrollPage = this.settings.capableScrollPage();
if (capableScrollPage) {
// 非全屏并且 y 轴方向偏移量小于 10 则同步用户手势的偏移量到 this.element 上
if (!this.fullsize && deltaY < 10) {
// screenX 是当前时间点用户手指所在的坐标点的 x 坐标
// this.endTouchX 来源有两处,一是 touchStart 中即用户手指刚接触屏幕时的坐标的 x
// 而是上一次手指移动的位置的坐标点的 x
const targetScrollLeft = screenX - this.endTouchX;
// console.log(
// 'epubjs - snap - onTouchMove - before',
// targetScrollLeft,
// this.element.scrollLeft
// );
// 用户从左往右滑动的时候,x 坐标是递减的,所以 targetScrollLeft 为负值,而为了让 this.element 产生向右偏移的效果,就需要减去负值,进而让 scrollLeft 有递增的趋势
// 用户从右往左滑动的时候,x 坐标是递增的,所以 targetScrollLeft 为正值,而为了让 this.element 产生向左偏移的效果,就需要减去正值,进而让 scrollLeft 有递减的趋势
this.element.scrollLeft -= targetScrollLeft;
// console.log(
// 'epubjs - snap - onTouchMove - after',
// targetScrollLeft,
// this.element.scrollLeft
// );
}
}
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchEnd(e, contents) {
if (this.lastTouchEndTime) {
const timeDelta = Date.now() - this.lastTouchEndTime;
if (timeDelta < 10) {
// console.log('got you!!');
return;
}
}
this.lastTouchEndTime = Date.now();
// console.log('epubjs - snap - onTouchEnd 来了哦', e, contents, Date.now());
// console.trace();
if (this.fullsize) {
this.disableScroll();
}
this.touchCanceler = false;
if (this.settings.capableScrollPage) {
const capableScrollPage = this.settings.capableScrollPage();
if (capableScrollPage) {
let swipped = this.wasSwiped();
this.swipped = swipped;
if (swipped !== 0) {
this.snap(swipped);
} else {
this.scrolling = false;
// TODO: cenfeng - 解决点击 epub 左右两侧没有反应的问题,因为这里 this.snap() 执行后会导致
// smoothScrollTo() 的执行,进而导致 scrollTo() 的执行,进而导致翻页已经成功后,又被重置回老的偏移量
// 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;
const result = left % snapWidth !== 0;
return result;
}
snap(howMany = 0) {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let snapTo = Math.round(left / snapWidth) * snapWidth;
if (howMany) {
// console.log('epubjs - snap - snap - 来了老弟之前', howMany, snapTo);
snapTo += howMany * snapWidth;
// console.log('epubjs - snap - snap - 来了老弟之后', howMany, snapTo);
}
this.snapTarget = snapTo;
// console.log('epubjs - snap - snap - howMany', howMany);
// console.log('epubjs - snap - snap - left', left);
// console.log('epubjs - snap - snap - snapWidth', snapWidth);
// console.log('epubjs - snap - snap - snapTo', snapTo);
// console.trace();
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);
// 如果用户 touch 事件或 resize 事件还未结束,则直接返回
if (this.touchCanceler || this.resizeCanceler) {
this.resizeCanceler = false;
this.snapping = false;
deferred.resolve();
return;
}
if (time < 1) {
window.requestAnimationFrame(tick.bind(this));
const scrollValue = start + (destination - start) * time;
this.snapTo = scrollValue;
// console.log('epubjs - snap - smoothScrollTo - time < 1', scrollValue);
this.scrollTo(scrollValue, 0);
} else {
// console.log('epubjs - snap - smoothScrollTo - time >= 1', destination);
this.snapTo = destination;
this.scrollTo(destination, 0);
this.snapping = false;
this.scrolling = false;
deferred.resolve();
this.manager.trimWaitingView();
}
}
tick.call(this);
return deferred.promise;
}
scrollTo(left = 0, top = 0) {
// console.log('xxx epubjs - scrollTo - snap', left);
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() {
if (!this.scroller) {
return;
}
if (this.fullsize) {
this.enableScroll();
}
this.removeListeners();
this.scroller = undefined;
}
}
EventEmitter(Snap.prototype);
export default Snap;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment