Comments (11)
Hello,
To build a slider of pages, I think we have to know the number of pages of the book, in a printing-like way (x of y).
I have written a little extension of the Renderer class to manage that kind of thing. It can be a little bit expensive in terms of resources because it needs to load ALL the contents of the book and to format/layout them.
Anyway, it is actually (and probably) the only way to reach it.
Inside this extension I put a couple of methods to get and set the percentage of reading, so the UI builder cna use them with a slider bar.
I copy here, this long extension.
@fchasen: I followed the coding style of the Renderer class, but if you want to edit it, don't hesitate to do it.
I extended the Renderer, because it seems the fastest way, but probably it could be rewritten as a new Class / Plugin, passing the "book" object to deal with.
I hope it can be useful.
/*
*
* It performs a calculation of printing-like pages number (x of y), loading silently
* and progressly all the contents of the book and formatting them to fit the actual render dimensions.
* You should call this function after the render is displayed and has formatted the layout (e.g. the first time of "renderer:chapterDisplayed").
* You can listen from it two events:
* "renderer:pagesNumChanged" -> dispatched after the operation is completed (returns the totalPages)
* "renderer:pagesNumProgress" -> dispatched during the operation (returns an object with pagesLoaded and pagesTotal)
*
* After having called this, you can use the utils/display methods to retrieve the formatted pages number, reading percentage etc.
* See below.
*
*/
EPUBJS.Renderer.prototype.calculateNumberOfPages = function(avoidCalcOnResize){
console.log("EPUBJS.Renderer.prototype.calculateNumberOfPages");
// destroy any previous operations
if (this.tempIFrame){
document.body.removeChild(render.tempIFrame);
this.tempIFrame = null;
}
// FLAG STATUS
this.isScanningComplete = false;
// the total pages of the book
this.totalPages = 0;
// loading index
this.currPageScanned = -1;
// object to store info
this.chaptersPages = {};
// timeout for delay in resize
this.resizeTimeOutID = -1;
//this.book.spine = [];
// add an event listener on resize, so we can compute again
if (!this.resizeEventAdded && avoidCalcOnResize !== true){
this.on("renderer:resized",this.onResizeNumPages,this);
this.resizeEventAdded = true;
}
// set as instance of Render so we can resolve it after the loading
this.promiseCalcPages = new RSVP.Promise();
// check if we have a copy in localStorage that fits the actual dimensions (in mobile device it's quite usual) so we can use it, without performing again the operation
var savedData = this.loadPagesNum();
if (savedData != null && !this.book.settings.reload){ // reload on settings.reload
this.isScanningComplete = true;
this.chaptersPages = savedData.chaptersPages;
this.totalPages = savedData.totalPages;
this.promiseCalcPages.resolve(this.totalPages);
this.lastPercent = savedData.lastPercent;
console.log("EPUBJS.Renderer.prototype.calculateNumberOfPages->using localStorage:",this.totalPages);
this.trigger("renderer:pagesNumChanged",this.totalPages);
this.book.trigger("renderer:pagesNumChanged",this.totalPages);
return this.promiseCalcPages;
}
this.calcNextPage();
return this.promiseCalcPages;
}
/* Return the page num for the first page of a chapter */
EPUBJS.Renderer.prototype.firstPageOfChapter = function(chapter){
if (!this.isScanningComplete) return -1;
console.log("firstPageOfChapter",this.chaptersPages[chapter.spinePos].firstPage, this.chapterPos);
return this.chaptersPages[chapter.spinePos].firstPage;
}
/* Return the current page */
EPUBJS.Renderer.prototype.getCurrentPage = function(){
if (!this.isScanningComplete) return -1;
return this.firstPageOfChapter(this.currentChapter) + this.chapterPos;
}
/* Return a formated page num like "1 / 120".
* You can pass a separator to format the result and/or a loading indicator in HTML
* You should call this functon everytime the page is changed ("renderer:pageChanged" and "renderer:pagesNumChanged")
*/
EPUBJS.Renderer.prototype.getCurrentPageOfTotPages = function(separator,loadingIndicator){
loadingIndicator || (loadingIndicator = "...");
if (!this.isScanningComplete) return loadingIndicator;
separator || (separator = " / ");
return this.getCurrentPage() + separator + this.totalPages;
}
/* Return a percent of reading (from 0 to 1)
* This could be usefull to build a slider.
*/
EPUBJS.Renderer.prototype.getReadingPercentage = function(){
if (!this.isScanningComplete) return null;
// to return a "real" 0 to 1 rappresentation we should force this to 0 even if, logically, the first page should be considered as read when displayed, so > 0.
if (this.getCurrentPage() == 1) return 0;
return this.getCurrentPage() / this.totalPages;
}
/*
* Go to page num of the book
*/
EPUBJS.Renderer.prototype.gotoPageNum = function(pageNum){
if (!this.isScanningComplete) return false;
var render = this;
for (var spinePos in this.chaptersPages) {
var chapter = this.chaptersPages[spinePos];
if (pageNum > chapter.firstPage && pageNum <= (chapter.firstPage+chapter.pages))
{
var chapterPos = pageNum - chapter.firstPage;
if (this.book.spinePos != spinePos){
this.book.spinePos = Number(spinePos);
this.book.displayChapter(this.book.spinePos).then(function(chapter){
render.page(chapterPos);
})
}
else{
this.page(chapterPos);
}
return true
}
};
return false;
}
/*
* Go to percentage of reading.
* percent => from 0 to 1;
* (usefull for slider bars)
*/
EPUBJS.Renderer.prototype.gotoPercent = function(percent){
var currPage = Math.round(this.totalPages*percent);
currPage = currPage == 0 ? 1 : currPage;
//console.log("EPUBJS.Renderer.prototype.gotoPercent",percent,currPage);
this.gotoPageNum(currPage);
}
// UTILS // INTERNAL //
EPUBJS.Renderer.prototype.onResizeNumPages = function(e){
console.log("EPUBJS.Renderer.prototype.onResizeNumPages");
clearTimeout(this.resizeTimeOutID);
// because the operation requires a lot of resources, we wait long enough before recalculating, being sure of the new dimensions
// Anyway flag as scanning
this.isScanningComplete = false;
this.resizeTimeOutID = setTimeout(this.recalcPages,2500,this); // pass the scope
}
EPUBJS.Renderer.prototype.recalcPages = function(render){
render.calculateNumberOfPages(true);
}
EPUBJS.Renderer.prototype.calcNextPage = function(){
if (this.book.spine[this.currPageScanned+1]){
this.currPageScanned++;
this.tempIFrame = document.createElement('iframe');
document.body.appendChild(this.tempIFrame);
this.tempIFrame.src = this.book.spine[this.currPageScanned].href;
this.tempIFrame.width = this.el.clientWidth;
this.tempIFrame.height = this.el.clientHeight;
this.tempIFrame.style.visibility = "hidden";
var render = this;
this.tempIFrame.onload = function(){
//console.log("PagesCalc-scanNext-onLoad");
var docEl = render.tempIFrame.contentDocument.documentElement;
var bodyEl = render.tempIFrame.contentDocument.body;
if(render.book.settings.fixedLayout) { // TODO, test if it works -> copied from Render.fixedLayout (probably we should use a seprated function to have a the same format)
docEl.style.width = render.tempIFrame.width;
//-- Adjust height
docEl.style.height = "auto";
//-- Remove columns
// this.docEl.style[EPUBJS.core.columnWidth] = "auto";
//-- Scroll
docEl.style.overflow = "auto";
}
else{ // TESTED -> copied from Render.formatSpread (probably we should use a seprated function to have a the same format)
//-- Clear Margins
bodyEl.style.margin = "0";
docEl.style.overflow = "hidden";
docEl.style.width = render.tempIFrame.width + "px";
//-- Adjust height
docEl.style.height = render.tempIFrame.height + "px";
//-- Add columns
docEl.style[EPUBJS.Renderer.columnAxis] = "horizontal";
docEl.style[EPUBJS.Renderer.columnGap] = render.gap+"px";
docEl.style[EPUBJS.Renderer.columnWidth] = render.colWidth+"px";
}
// calc pages
var totalWidth = docEl.scrollWidth;
var displayedPages = Math.ceil(totalWidth / render.spreadWidth);
// save with index, so we can retrieve after from spinePos
render.chaptersPages[render.book.spine[render.currPageScanned].index] = {firstPage:render.totalPages,pages:displayedPages};
render.totalPages += displayedPages;
//console.log("scanNext-pages",displayedPages,render.book.spine[render.currPageScanned].href);
document.body.removeChild(render.tempIFrame);
render.tempIFrame = null;
var progEventData = {pagesLoaded:(render.currPageScanned+1),pagesTotal:render.book.spine.length}
render.trigger("renderer:pagesNumProgress", progEventData);
render.book.trigger("renderer:pagesNumProgress", progEventData);
render.calcNextPage();
}
}
else{
this.isScanningComplete = true;
this.promiseCalcPages.resolve(this.totalPages);
this.trigger("renderer:pagesNumChanged",this.totalPages);
this.book.trigger("renderer:pagesNumChanged",this.totalPages);
this.savePagesNum();
}
}
/* save in localStorage */
EPUBJS.Renderer.prototype.savePagesNum = function(){
if (!localStorage || !this.isScanningComplete) return;
var pagesNumKey = this.book.settings.bookPath + ":pages:" + this.book.settings.version;
var pagesObject = {width:this.el.clientWidth, height:this.el.clientHeight, totalPages:this.totalPages, chaptersPages:this.chaptersPages, lastPercent:this.getReadingPercentage()};
localStorage.setItem(pagesNumKey,JSON.stringify(pagesObject))
}
/* load from localStorage */
EPUBJS.Renderer.prototype.loadPagesNum = function(){
if (!localStorage) return null;
var pagesNumKey = this.book.settings.bookPath + ":pages:" + this.book.settings.version,
fromStore = localStorage.getItem(pagesNumKey);
if(fromStore && fromStore != 'undefined' && fromStore != 'null'){
var pagesObject = JSON.parse(fromStore);
// we can use it only if we are at the same dimensions we had calculate before
if (pagesObject.width == this.el.clientWidth && pagesObject.height == this.el.clientHeight){
return pagesObject;
}
}
return null;
}
from epub.js.
Can you please update in this version? So I can download & see the output.
from epub.js.
You can copy the code above in a file and embed it in your html page after the epub.js script tag, like a sort of plugin.
Then, when you have a EPUBJS.Book instance with a valid renderer you can call these lines (anyway I recommend you to do it only when you are sure the layout of the book is done).
book.on("renderer:pagesNumChanged", onPageChange);
book.render.calculateNumberOfPages();
function onPageChange()
{
console.log(book.render.getCurrentPageOfTotPages(" / ", "(...)"));
}
from epub.js.
Sorry I haven't had a chance to review this yet.
We were thinking there might be a way to get close enough to create a slider by just getting the file-size of each chapter and estimating based on that.
from epub.js.
Before writing that, I have also managed to implement the file size method, because, for sure, it's considerably less expensive. But I was not able to get an accurate estimation for pages number. With books heavily formatted (a lot of new lines, dialogues etc.) and, moreover, with illustrated books the estimation is quite inaccurate for me to consider that as a good result.
Probably to manage merely the percentage (not the exact pages number), the file size method could reach quite good results (not with illustrated ebooks). Anyway, to simulate a "real" page number (the one the reader is wont to refer), I guess it's not suitable.
from epub.js.
Another way you could do it would be to load and render the entire epub once, generate a CFI for each "page" (say, the first visible element) and then use the list of generated CFIs as indexes in the slider (or whatever other sort of navigation control).
Of course this is also an approximation, but probably a decent one under the assumption that a single user will likely be reading with the same screen each time. Changing font and style settings, margins, synthetic layout would also affect the approximation, but so it will with any method except for recalculating the number of pages dynamically (which requires the entire EPUB to be loaded in memory, as pointed out).
The other benefit of this method is that you could (I think) generate the CFIs using a headless browser for a variety of different screen sizes/style settings and have them ready to go when the user loads the epub. So, for example, a list of CFIs for a single page layout on an iPhone, iPad, iPad mini etc. It might provide a better estimation than inferring from the size of un-rendered xhtml and would be more performant than having to load the entire epub.
You could also do this without CFIs, but tying your "page" reference to a specific element at least provides some guarantee that you can navigate back to a position familiar to a user. Using CFIs also means that users can share their "page" reference with another user (who has a different screen size and "number of pages") and still be able to point them at about the same content.
from epub.js.
I suppose there isn't really a way around loading and rendering the whole book to get the page numbers, though I think for most instances a less accurate progress indicator should suffice.
Finding the CFI for each page would be even more expensive then just rendering them, but it would certainly be more useful and I love the idea of being able to pre-populate it for certain screen size / fixed width book.
I'll try to split up the renderer to be able to be more flexible in regards to being "headless" and make some tests to see how quickly we can generate a cfi for every page.
from epub.js.
Definitely true that it would be more expensive. I should have clarified my assumption, which is that it would only be used to pre-populate it for certain screen sizes. Generating CFIs for every page every time a user opens and epub would, as you said, require loading the whole epub, with CFI generation overhead on top of that.
from epub.js.
Regardless of the way you will choose to develop the paging system, be careful that the current implementation of CFI fails to restore the exact position within small resolutions, mostly when there are long paragraphs/elements inside a page/column. In my iPhone, with some ePubs it fails about 1 of 15 times.
from epub.js.
Closing this for now as much what @panurge-ws commented on was incorporated in in the Generate Pagination method.
However, I think the pagination methods still need much improvement and discussion. For a first step I'm hoping to create a rough pagination method based on file size.
from epub.js.
Hi. I am sorry to post in this closed issue but can anyone please help me implement a paging slider. I tried @panurge-ws way of implementation above but could not get it working. Can someone please guide me.
from epub.js.
Related Issues (20)
- Shift right when selection text HOT 3
- Bug: When package manifest and toc are in different folders, navigation does not work.
- Unable to read renderless.html file when i change the url to my book HOT 2
- Is `ignoreClass` value within the examples outdated? HOT 1
- What happened to the official website? The content is very strange. HOT 2
- Continuous Manager inside an element that is scrolling that is not the viewport?
- Scripted Content SandBoxing issue, And CFI pages position
- Extracting the text per rendered section / block using epub js? HOT 32
- How do I add a paginated swipe animation?
- highlight not working post integrating with hammer.js for swiping HOT 1
- how to call images in css file? HOT 1
- about rendition.next(), rendition.prev()
- about InlineView
- Minified XML causes 'container.xml not found' HOT 4
- renderTo CSS selector instead of ID HOT 1
- [Bug] Blank page when resize happen during loading
- How to add any emoji on page ?
- How to add any emoji on page? HOT 2
- how to turn the page by wheel
- rapidly scroll up and down will cause error in continuous-scrolled example
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from epub.js.