Reputation: 404
I am trying to emulate a multi-page editor using dynamic cloning of contenteditable divs. It's running very smooth on Chrome, but on Firefox, crashing:
I was not able even to debug what is crashing on Firefox. What can I do to fix that?
Thanks in advance !
Upvotes: 1
Views: 95
Reputation: 815
I tried the example on Firefox and it crashed when I reached a new page. The reason behind this is, that pages
HTMLCollection is being manipulated inside a for loop. This is similar to this issue.
As this suggests, you can avoid the issue if you use backwards iteration instead. The best solution however is to not to use iteration at all. You can dynamically attach an event listener when a new page is created, and you can access all of the siblings of that page.
Also, in your example new lines were displayed differently on Chrome and Firefox. Chrome added a new <div>
for every line, Firefox was using <br>
. Changing the DefaultParagraphSeparator
to div
results in identical lines on both browsers.
This example works on both browsers:
function redator(divId) {
const root = document.getElementById(divId)
const a4 = {
height: 830,
width: 595,
lineHeight: 30
};
const getChildrenHeight = (element) => {
total = 0;
if (element.childNodes) {
for (let child of element.childNodes) {
switch (child.nodeType) {
case Node.ELEMENT_NODE:
total += child.offsetHeight;
break;
case Node.TEXT_NODE:
let range = document.createRange();
range.selectNodeContents(child);
rect = range.getBoundingClientRect();
total += (rect.bottom - rect.top);
break;
}
}
}
return total;
};
const setSelection = (node, offset) => {
let range = document.createRange();
let sel = window.getSelection();
range.setStart(node, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
const addPage = () => {
const firstPage = root.querySelector('#page-1');
const newPage = firstPage.cloneNode(true);
const pages = root.getElementsByClassName('page');
newPage.addEventListener('input', onInput);
newPage.innerHTML = '';
newPage.id = 'page-' + (pages.length + 1);
firstPage.parentNode.appendChild(newPage);
newPage.focus();
newPage._emptyPage = true;
return newPage;
}
function onInput(e) {
const page = this;
const previousPage = page.previousElementSibling;
const nextPage = page.nextElementSibling;
const pageHeight = getChildrenHeight(page);
const lastChild = page.lastChild;
const cloneChild = lastChild.cloneNode(true);
const textContent = page.innerText;
if ((pageHeight === 0 || textContent.length <= 1) && !!previousPage && !page._emptyPage) {
page.remove();
previousPage.focus();
const lastChild = previousPage.lastChild;
setSelection(lastChild, lastChild.childNodes.length);
} else if (pageHeight > a4.height && !nextPage) {
lastChild.remove();
addPage().appendChild(cloneChild);
} else if (pageHeight > a4.height && nextPage) {
lastChild.remove();
nextPage.insertBefore(cloneChild, nextPage.firstChild);
let selection = getSelection().getRangeAt(0).startContainer.parentElement.closest('div');
if(selection === page.lastChild) {
setSelection(cloneChild, 0);
}
} else if (pageHeight < a4.height - a4.lineHeight && !!nextPage) {
let firstChildOfNextPage = nextPage.firstChild;
let clone = firstChildOfNextPage.cloneNode(true);
firstChildOfNextPage.remove();
page.appendChild(clone);
}
page._emptyPage = false;
}
document.execCommand("DefaultParagraphSeparator", false, "div");
root.querySelector('#page-1').addEventListener('input', onInput);
}
redator('editor');
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
#editor {
background-color: gray;
border: 1px black;
padding: 1em 2em;
}
.page {
background-color: white;
border: solid black;
padding: 1em 2em;
width: 595px;
height: 841px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
</style>
</head>
<body>
<h3>My Editor</h3>
<div id="editor">
<div contenteditable="true" class="page" id="page-1">
<b>hello</b>
</div>
</div>
</body>
</html>
Upvotes: 1