Open ncammarata opened 6 years ago
looks like https://github.com/futurepress/epub.js/issues/728 solves it!
weird bit of synchronicity, I am trying to solve exactly this problem right now too!
However, #728 doesn't seem to work for me, I get:
console.log(range)
VM7985:1 Range {startContainer: text, startOffset: 70, endContainer: text, endOffset: 197, collapsed: false, …}
undefined
range.getClientBoundingRect();
VM7987:1 Uncaught TypeError: range.getClientBoundingRect is not a function
at eval (eval at $scope.onRenditionSelected (angularEpubReader.js:280), <anonymous>:1:7)
at Rendition.$scope.onRenditionSelected (angularEpubReader.js:280)
at Rendition.emit (epubnew.js:2152)
at Rendition.triggerSelectedEvent (epubnew.js:7403)
at Contents.<anonymous> (epubnew.js:7371)
at Contents.emit (epubnew.js:2160)
at Contents.triggerSelectedEvent (epubnew.js:4847)
at Contents.<anonymous> (epubnew.js:4828)
(anonymous) @ VM7987:1
$scope.onRenditionSelected @ angularEpubReader.js:280
emit @ epubnew.js:2152
triggerSelectedEvent @ epubnew.js:7403
(anonymous) @ epubnew.js:7371
emit @ epubnew.js:2160
triggerSelectedEvent @ epubnew.js:4847
(anonymous) @ epubnew.js:4828
setTimeout (async)
onSelectionChange @ epubnew.js:4826
Whoops. I was using a older/different version of epub.js. This works fine in the "highlight" example from the repo.
it turns out that this gives the position relative to the iframe, not relative to the page. For the Google docs-type experience, I need it relative to the page. Would love help here if anyone knows a way!
had the same issue!
the way i solved it was by maintaining a reference on my state to the latest view.element
emitted by the rendition rendered event, then using that as an offset against range.getBoundingClientRect()
.
Im pretty sure thats the only way to do it, as the latest "rendered" will definitely contain the view element that is visible to the user (pretty sure).
So to solve your problem you might do something like this:
let cfiRange = "epubcfi(....)";
let latestViewElement;
rendition.on("rendered", (section, view) => {
latestViewElement = view.element;
});
const getCfiRangeBounds = () => {
if(!latestViewElement) throw new Error("No view element yet")
const contents = rendition.getContents()[0];
if(!contents) throw new Error("Why no contents??");
const range = contents.range(cfiRange);
if(!range) throw new Error("Input range is not in current view");
const bounds = range.getBoundingClientRect();
const viewElementBounds = latestViewElement.getBoundingClientRect();
return {
top: Math.abs(bounds.top) - Math.abs(viewElementBounds.top),
left: Math.abs(bounds.left) - Math.abs(viewElementBounds.left)
};
}
Not sure if it will fix your exact issue, as this is only tested on a paginated 2 column layout. Hope it helps!
Instead of keeping a latestViewElement
, which doesn't work sometimes with continuous scrolled layout, getting the position of the frame seems to work well:
const section = book.spine.get(cfi)
const frameBounds = section.document.defaultView.frameElement.getBoundingClientRect()
// top: bounds.top + frameBounds.top
// etc.
@johnfactotum I'm wondering how you are getting defaultView populated from the section document? When I follow your code I get null
as the defaultView. I'm trying to get the position in a continuous scrolled layout.
I not sure but I think one needs to use rendition.getContents()
and obtain the contents object (similar to @dmisdm's code above), then use contents.document.defaultView.frameElement
. In continuous scrolled layout I think you will often get more than one contents when two or more pages are displayed at the same time, and you need to figure out which one is the one you need.
In my own code I use it in a hook like this, and the hook will provide you with the right content object:
rendition.hooks.content.register((contents, view) => {
const frame = contents.document.defaultView.frameElement
const viewElementRect = frame.getBoundingClientRect()
// `target` can be an element or a range
const rect = target.getBoundingClientRect()
const left = rect.left + viewElementRect.left
// etc.
}
I would like to implement annotations a la Google Docs where you can just scroll the range into view. However, this requires me to convert from cfi -> position on screen. Is this possible?