I do know the title isnt clear however what im basically attempting to do is do my very own rendering for an EPUB and im at present taking a naive strategy in the direction of paginating the chapter content material and attempting to render it in a WebView after splitting the content material primarily based on the tags.
My subject begins from the purpose the place i can’t appear to precisely break up the content material to suit into the web page, and it overflows within the rendered net view.
I’ve a separate net view which renders off display screen and calculates the scrollHeight of the div the place the content material is being injected in.
The web page is mainly constructed block by block (tag by tag) till the div begins to overflow and the peak of the div is larger than the View that its rendered to.
The pagination perform yields the content material collected simply earlier than the peak is exceeded.
The issue is that it by no means suits to display screen and there may be all the time a little bit of vertical overflow left.
My goal is to in the end generate a listing of animated WebViews the consumer can scroll by means of horizontally like an precise web page flip of a guide.
I’d actually respect some assist in figuring out the logic or calculation flaw right here within the structure top comparisons. Let me know if ive missed one thing.
Under is the react native part which does the pagination and rendering
import { EPUBChapter, epubHtmlTemplate, ParsedEPUB } from "@/lib/EpubParser";
import { useEffect, useRef, useState } from "react";
import { parseDocument, DomUtils } from "htmlparser2";
import render from "dom-serializer";
import { WebView } from "react-native-webview";
import { Textual content } from "./ui/textual content";
import { useHtmlHeightMeasurer } from "@/hooks/useHtmlHeightMeasurer";
import { View, PixelRatio, FlatList } from "react-native";
export perform WebViewCarousel({
parsedEpub,
currentChapterIndex = 0,
}: {
parsedEpub: ParsedEPUB;
currentChapterIndex: quantity;
}) {
const chapters = parsedEpub.chapters;
const [currentChapter, setCurrentChapter] = useState(
undefined
);
const view = useRef(null);
// const { html } = useHtmlHeightMeasurer(viewDimensions.width, viewDimensions.top);
const { measureHtmlHeight, MeasuringWebView, resizeWebView } =
useHtmlHeightMeasurer(355, 355);
const [pages, setPages] = useState([]);
async perform* pageGenerator(
chapterHtml: string,
dimensions: { width: quantity; top: quantity }
) {
resizeWebView({ top: dimensions.width, width: dimensions.top });
const parsedChapterHtml = parseDocument(chapterHtml);
const htmlNodes = parsedChapterHtml.kids;
let pageNodes: typeof parsedChapterHtml.kids = [];
const chapterContent = DomUtils.findOne(
(elem) => elem.identify === "part",
htmlNodes
);
// const pixelHeight = PixelRatio.getPixelSizeForLayoutSize(dimensions.top);
const pixelHeight = dimensions.top;
if (chapterContent) {
for (const [index, node] of chapterContent.kids.entries()) {
const testPage = render([...pageNodes, node]);
// console.log('Node: ' + render(node));
if (testPage.trim()) {
const webViewHeight = await measureHtmlHeight(
epubHtmlTemplate(testPage)
);
console.log("View top: " + pixelHeight);
console.log("Measured Peak: " + webViewHeight);
if (webViewHeight >= pixelHeight) {
console.log("Yielding Web page");
yield pageNodes;
pageNodes = [];
}
pageNodes.push(node);
}
}
}
console.log("Remaining web page");
return pageNodes;
}
// const generatePage = pageGenerator(chapters[currentChapterIndex].content material);
async perform generatePages({
width,
top,
}: {
width: quantity;
top: quantity;
}) {
const pagesToAdd = [];
for await (const web page of pageGenerator(
chapters[currentChapterIndex].content material,
{ width, top }
)) {
console.log(render(web page));
pagesToAdd.push(render(web page));
}
setPages([...pages, ...pagesToAdd]);
}
return (
{
generatePages({
top: e.nativeEvent.structure.top,
width: e.nativeEvent.structure.width,
});
}}
>
{pages.size > 0 && (
)}
);
}
The offscreen part used to measure the content material till it overflows
import { epubHtmlTemplate } from "@/lib/EpubParser";
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { WebView } from "react-native-webview";
const measureJs = `
(() => {
const observer = new MutationObserver(measure)
let debounce;
perform measure() {
clearTimeout(debounce);
debounce = setTimeout(() => {
strive {
const container = doc.getElementById('epub-content');
if(container) {
const top = container.scrollHeight;
window.ReactNativeWebView.postMessage(JSON.stringify({
sort: 'measurement',
worth: top
}));
} else {
window.ReactNativeWebView.postMessage(JSON.stringify({
sort: 'error',
worth: 'Lacking container with id epub-content'
}));
}
} catch (error) {
window.ReactNativeWebView.postMessage(JSON.stringify({
sort: 'error',
worth: error.message
}));
}
}, 150);
}
observer.observe(doc.physique, {
childList: true,
subtree: true,
attributes: true
});
measure();
})()
`;
export perform useHtmlHeightMeasurer(width: quantity, top: quantity) {
const [html, setHtml] = useState("");
const resolver = useRef<(h: quantity) => void>();
const webViewRef = useRef(null);
const [webViewDimensions, setWebViewDimensions] = useState({
top: 0,
width: 0
});
const measureHtmlHeight = (htmlString: string) =>
new Promise((resolve) => {
resolver.present = resolve;
setHtml(htmlString);
});
const resizeWebView = ({ width, top }: { width: quantity, top: quantity }) => {
setWebViewDimensions({ width, top })
}
const MeasuringWebView = () => (
{
const information = JSON.parse(occasion.nativeEvent.information);
if(information.sort === 'error') {
console.error(information.worth)
}
if (information.sort === "measurement") {
const h = Quantity(information.worth);
if (resolver.present) {
resolver.present(h);
resolver.present = undefined;
}
}
}}
fashion={{ width: webViewDimensions.width, top: webViewDimensions.top, backgroundColor: "clear" }}
/>
);
return { measureHtmlHeight, MeasuringWebView, resizeWebView };
}