Show a caret in a custom textarea without displaying its text - javascript

I have a custom textarea. In this example, it makes the letters red or green, randomly.
var mydiv = document.getElementById('mydiv'),
myta = document.getElementById('myta');
function updateDiv() {
var fc;
while (fc = mydiv.firstChild) mydiv.removeChild(fc);
for (var i = 0; i < myta.value.length; i++) {
var span = document.createElement('span');
span.className = Math.random() < 0.5 ? 'green' : 'red';
span.appendChild(document.createTextNode(myta.value[i]));
mydiv.appendChild(span);
}
};
myta.addEventListener('input', updateDiv);
body { position: relative }
div, textarea {
-webkit-text-size-adjust: none;
width: 100%;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
font: 1rem sans-serif;
padding: 2px;
margin: 0;
border-radius: 0;
border: 1px solid #000;
resize: none;
}
textarea {
position: absolute;
top: 0;
color: transparent;
background: transparent;
}
.red { color: #f00 }
.green { color: #0f0 }
<div id="mydiv"></div>
<textarea id="myta" autofocus=""></textarea>
There's an output div with a textarea over it. So the textarea doesn't cover up any of the colorful things below it, its color and background are set to transparent. Everything works here, except that the caret (the flashing cursor provided by the user agent) is transparent.
Is there a way to show the caret without making the textarea's text visible?
If I make the div above the textarea instead and give it pointer-events: none, the textarea is still visible underneath. This arrangements also makes smooth scrolling difficult, so it doesn't work for me.

Just insert your own caret!
function blink() {
document.getElementById('caret').hidden ^= 1;
blinkTimeout = setTimeout(blink, 500);
}
var mydiv = document.getElementById('mydiv'),
myta = document.getElementById('myta'),
blinkTimeout = setTimeout(blink, 500),
lastSelectionStart = 0,
lastSelectionEnd = 0,
whichSelection = true;
function updateDiv() {
var fc;
while (fc = mydiv.firstChild) mydiv.removeChild(fc);
if (myta.selectionStart != lastSelectionStart) {
lastSelectionStart = myta.selectionStart;
whichSelection = false;
}
if (myta.selectionEnd != lastSelectionEnd) {
lastSelectionEnd = myta.selectionEnd;
whichSelection = true;
}
var cursorPos = whichSelection ? myta.selectionEnd : myta.selectionStart;
for (var i = 0; i < myta.value.length; i++) {
if (i == cursorPos) {
var caret = document.createElement('span');
caret.id = 'caret';
caret.appendChild(document.createTextNode('\xA0'));
mydiv.appendChild(caret);
clearTimeout(blinkTimeout);
blinkTimeout = setTimeout(blink, 500);
}
var span = document.createElement('span');
span.className = Math.random() < 0.5 ? 'green' : 'red';
span.appendChild(document.createTextNode(myta.value[i]));
mydiv.appendChild(span);
}
if (myta.value.length == cursorPos) {
var caret = document.createElement('span');
caret.id = 'caret';
caret.appendChild(document.createTextNode('\xA0'));
mydiv.appendChild(caret);
clearTimeout(blinkTimeout);
blinkTimeout = setTimeout(blink, 500);
}
};
myta.addEventListener('input', updateDiv);
myta.addEventListener('focus', updateDiv);
myta.addEventListener('mousedown', function() {
setTimeout(updateDiv, 0);
});
myta.addEventListener('keydown', function() {
setTimeout(updateDiv, 0);
});
myta.addEventListener('blur', function() {
document.getElementById('caret').hidden = true;
clearTimeout(blinkTimeout);
});
body { position: relative }
div, textarea {
-webkit-text-size-adjust: none;
width: 100%;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
font: 1rem sans-serif;
padding: 2px;
margin: 0;
border-radius: 0;
border: 1px solid #000;
resize: none;
}
textarea {
position: absolute;
top: 0;
color: transparent;
background: transparent;
}
.red { color: #f00 }
.green { color: #0f0 }
#caret {
display: inline-block;
position: absolute;
width: 1px;
background: #000;
}
#caret[hidden] { display: none }
<div id="mydiv"><span id="caret"> </span></div>
<textarea id="myta" autofocus=""></textarea>
I have here a <span> #caret inserted into the div which blinks every 500ms by toggling its hidden attribute using JS. To replicate browser behavior, I had to detect whether it was the selectionStart or the selectionEnd which the caret was actually at, and make it remain solid while text was being input.
This is a bit harder to achieve when the spans aren't of fixed length or are nested, but it's easier than fiddling with contentEditable with a more complex highlighter. This function will insert the caret in the right spot:
function insertNodeAtPosition(node, refNode, pos) {
if (typeof(refNode.nodeValue) == 'string') refNode.parentNode.insertBefore(node, refNode.splitText(pos));
else {
for (var i = 0; i < refNode.childNodes.length; i++) {
var chNode = refNode.childNodes[i];
if (chNode.textContent.length <= pos && i != refNode.childNodes.length - 1) pos -= chNode.textContent.length;
else return insertNodeAtPosition(node, chNode, pos);
}
}
}
Usage (where i is the position to insert it):
var caret = document.createElement('span');
caret.id = 'caret';
caret.appendChild(document.createTextNode('\xA0'));
insertNodeAtPosition(caret, mydiv, i);
clearTimeout(blinkTimeout);
blinkTimeout = setTimeout(blink, 500);

Why not simply use a <div contenteditable="true"></div> instead <textarea></textarea>?. With this you don't need the extra textarea. See a demo here.
HTML:
<div id="myta" autofocus="" contenteditable="true"></div>
JavaScript:
var myta = document.getElementById('myta');
function updateDiv() {
var fc;
var text = myta.innerText || myta.textContent;
while (fc = myta.firstChild) myta.removeChild(fc);
for (var i = 0; i < text.length; i++) {
var span = document.createElement('span');
span.className = Math.random() < 0.5 ? 'green' : 'red';
span.appendChild(document.createTextNode(text[i]));
myta.appendChild(span);
}
placeCaretAtEnd(myta);
};
myta.addEventListener('input', updateDiv);
Also, to move the caret at the end when you put the new text inside the div I used that function from this answer:
function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}

Related

Slow performance Input event in contenteditable DIV

I have a DIV which has contenteditable attribute and I am running 70 lines of JavaScript code on input event but when I try to type fast. The JavaScript code is very slow and the results don't show/update in the UI as quickly as expected.
What I am doing in the JavaScript is in the input event:
I am getting the textcontent of the DIV with contenteditable attribute and then changing it to array with split() function and then, by using a for loop, I am comparing the array values with other array. If the value matches I create a SPAN with textContent set as the current value from that contenteditable DIV and then put it in an spanArray array. Then I'm appending all those SPANs into the contenteditable DIV and setting the caret at the end.
How to optimize the code so that the type speed / performance is not affected by the JavaScript's heavy for loops seen in the below example?
Here is the code I got so far:
const mainDiv = document.querySelector("#mainDiv");
const placeHolderDiv = document.querySelector("#placeholder");
let placeHolder = "";
let placeHolderArr;
fetch("https://type.fit/api/quotes")
.then((data) => {
return data.json();
})
.then((result) => {
for (let quote = 0; quote < 10; quote++) {
placeHolder += result[quote].text;
placeHolderArr = placeHolder.split(" ");
placeHolderDiv.textContent = placeHolder;
}
});
mainDiv.addEventListener("input", (e) => {
let spanArr = [];
const mainDivArr = mainDiv.textContent.split(" ");
let num = 0;
if (e.code !== "Space") {
for (let i of mainDivArr) {
if (placeHolderArr[num].trim() === mainDivArr[num].trim()) {
const span = document.createElement("span");
span.textContent = mainDivArr[num] + " ";
mainDiv.innerHTML = "";
spanArr.push(span);
num++;
} else if (placeHolderArr[num].trim() !== mainDivArr[num].trim()) {
const span = document.createElement("span");
span.style.color = "red";
span.textContent = mainDivArr[num] + " ";
mainDiv.innerHTML = "";
spanArr.push(span);
num++;
}
for (let spans of spanArr) {
mainDiv.innerHTML += spans.outerHTML;
// Placing Caret At The End
function placeCaretAtEnd(el) {
el.focus();
if (
typeof window.getSelection != "undefined" &&
typeof document.createRange != "undefined"
) {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
placeCaretAtEnd(mainDiv);
}
}
} else {
console.log("space pressed");
}
});
body {
height: 90vh;
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
font-size: 1.5rem;
}
#content {
position: relative;
color: grey;
width: 70vw;
}
#mainDiv {
position: absolute;
top: 0;
left: 0;
color: #000;
width: 100%;
border: none;
outline: none;
}
<div id="content">
<div id="mainDiv" autofocus contenteditable></div>
<div id="placeholder"></div>
</div>
Consider another approach to the problem.
Instead of doing heavy operations over a contenteditable, disrupting such an area while the user is inserting text / typing, using two nested heavy for loops (O(n^2) complexity), and defining functions within a for loop (all really bad practices), I would suggest:
use only one DIV element
populate it with SPANs for words and characters
set it as tabindex="1" so that you can focus it and capture keyboard events
create an index variable idx so you can keep track on the progress and add classes to every character SPAN
on "keydown" event, use Event.key to determine the pressed key character. If the character matches with the current expected character, advance the index
const text = `Focus and start typing. Genius is one percent inspiration and ninety-nine percent perspiration.`;
const elText = document.querySelector("#text");
const textChars = [];
const words = text.split(/(?<=\S+\s)/g);
// Create SPAN for words and characters
elText.innerHTML = words.reduce((html, word) => {
textChars.push(...word);
const chars = [...word].map(ch => `<span class="char">${ch}</span>`).join("");
return html + `<span class="word">${chars}</span>`;
}, "");
const elsChars = elText.querySelectorAll(".char");
const totChars = textChars.length;
let totIncorrect = 0;
let isGameOver = false;
let idx = 0; // type progress index
elsChars[idx].classList.add("current");
elText.addEventListener("keydown", (ev) => {
if (isGameOver) return; // Prevent any more typing
ev.preventDefault(); // Prevent spacebar scrolling etc...
let typeChar = ev.key;
if (typeChar === "Enter") typeChar = " "; // Treat Enter as Space
if (typeChar.length > 1) return; // Do nothing on special keys
// CORRECT:
if (typeChar === textChars[idx]) {
elsChars[idx].classList.add("ok");
}
// INCORRECT:
else {
elsChars[idx].classList.add("err");
totIncorrect += 1;
}
// ADVANCE:
elsChars[idx].classList.remove("current");
idx += 1;
elsChars[idx]?.classList.add("current");
// GAME OVER:
if (idx === totChars) {
isGameOver = true;
console.log(`Well done! You had ${totIncorrect} mistakes out of ${totChars}!`);
}
});
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font: 1.5rem/1.3 sans-serif;
}
#text {
position: relative;
padding: 1rem;
color: grey;
width: 60vw;
color: #888;
letter-spacing: 0.15rem;
}
#text:focus {
outline: 1px solid #ddd;
}
.char {
display: inline;
white-space: pre-wrap;
}
#text:focus .current {
animation: blink 1s infinite;
}
#keyframes blink {
0% {box-shadow: -3px 0 0 #000;}
50% {box-shadow: -3px 0 0 #000;}
100% {box-shadow: -3px 0 0 transparent;}
}
.ok {
color: #000;
background: hsla(200 , 100%, 50%, 0.1);
}
.err {
color: #f00;
background: hsla(0, 100%, 50%, 0.1);
}
<div id="text" tabindex="1"></div>
With this simpler solution, now you can type as fast as it goes :)
For mobile devices you could incorporate an overlay transparent textarea in order to grab focus and prompt the onscreen keyboard.

How to sort elements on DOM by its inner Text

I have a graph that is rendering its values as a div inside the body element with a class according to their number values. This is working fine. But next I need to sort the divs according to their number values or background color. BUT, it needs to start on the lower left corner of the page and fan out upwards to towards the right as the numbers increase. Basically just like a line graph.
I'd like to stay away from libraries if at all possible.
How would I approach this? Thank you all.
let interval = setInterval(makeDivs, 5);
function makeDivs(){
let cont = checkHeight();
if(cont){
let div = document.createElement('div');
let randNum = Math.random() * 100;
if(randNum < 20) { div.classList.add('blue') }
if(randNum >= 20 && randNum < 40) { div.classList.add('green') }
if(randNum >= 40 && randNum < 60) { div.classList.add('yellow') }
if(randNum >= 60 && randNum < 80) { div.classList.add('orange') }
if(randNum >= 80 && randNum < 101) { div.classList.add('red') }
div.textContent = randNum.toFixed(2);
document.querySelector('body').appendChild(div);
} else {
alert('done');
clearInterval(interval);
sortDivs(); // Begin sorting divs
}
}
function checkHeight(){
let w = window.innerHeight;
let b = document.querySelector('body').offsetHeight;
if(b < w) {
return true;
} else {
return false;
}
}
function sortDivs(){
document.querySelector("body div:last-child").remove();
alert('sorting now...')
}
* { box-sizing: border-box;}
body { width: 100vw; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: end;}
body div { width: calc(10% + 1px); text-align: center; border: 1px solid #ddd; margin: -1px 0 0 -1px; padding: 10px;}
body div.blue { background: aqua; }
body div.green { background: green; }
body div.yellow { background: yellow; }
body div.orange { background: orange; }
body div.red { background: red; }
UPDATE!!!
So I have this so far based on the feed back down below. The problem now is the sorting is only happening laterally and not on an angle (spreading right and to the top).
let interval = setInterval(makeDivs, 10);
function makeDivs(){
let cont = checkHeight();
if(cont){
let div = document.createElement('div');
let randNum = Math.random() * 100;
if(randNum < 20) { div.classList.add('blue') }
if(randNum >= 20 && randNum < 40) { div.classList.add('green') }
if(randNum >= 40 && randNum < 60) { div.classList.add('yellow') }
if(randNum >= 60 && randNum < 80) { div.classList.add('orange') }
if(randNum >= 80 && randNum < 101) { div.classList.add('red') }
div.textContent = randNum.toFixed(2);
document.querySelector('.outPut').appendChild(div);
} else {
clearInterval(interval);
document.querySelector(".outPut div:last-child").remove();
compileArrays(); // Begin sorting divs
}
}
function checkHeight(){
let w = window.innerHeight;
let b = document.querySelector('.outPut').offsetHeight;
if(b < w) {
return true;
} else {
return false;
}
}
function compileArrays(){
let divs = document.querySelectorAll('.outPut div');
let bArr = [], gArr = [], yArr = [], oArr = [], rArr = [];
divs.forEach( (d) => {
if( d.classList.contains('blue') ){ bArr.push(d) }
if( d.classList.contains('green') ){ gArr.push(d) }
if( d.classList.contains('yellow') ){ yArr.push(d) }
if( d.classList.contains('orange') ){ oArr.push(d) }
if( d.classList.contains('red') ){ rArr.push(d) }
});
let finalArr = sortArray(bArr).concat(sortArray(gArr)).concat(sortArray(yArr)).concat(sortArray(oArr)).concat(sortArray(rArr));
newDom(finalArr);
}
function sortArray(arr){
let newArr = arr;
newArr.sort( (a, b) => {
return a.innerText - b.innerText;
});
return newArr;
}
function newDom(arr){
let b = document.querySelector('.outPut');
b.innerHTML = '';
arr.reverse();
arr.forEach((a) => {
b.appendChild(a);
});
}
* { box-sizing: border-box;}
body { width: 100vw; height: 100vh; margin: 0; padding: 0; display: flex; align-items: flex-end;}
body .outPut { flex: 1; display: flex; flex-wrap: wrap; flex-direction:row-reverse; }
body .outPut div { width: calc(10% + 1px); text-align: center; border: 1px solid #ddd; margin: -1px 0 0 -1px; padding: 10px;}
body .outPut div.blue { background: aqua; }
body .outPut div.green { background: #44df15; }
body .outPut div.yellow { background: yellow; }
body .outPut div.orange { background: orange; }
body .outPut div.red { background: red; }
<div class="outPut"></div>
Supposed you already have a mechanism to organise such DIVs in a grid as shown, the following should give you what you are looking for:
var items = divList.filter((div) => div.nodeType == 1); // get rid of the whitespace text nodes
items.sort(function(a, b) {
return a.innerHTML == b.innerHTML
? 0
: (a.innerHTML > b.innerHTML ? 1 : -1);
});
Then, place them back in the DOM as needed, example:
for (i = 0; i < items.length; ++i) {
divList.appendChild(items[i]);
}
This worked with the first code example!!!
try this sortDivs function:
function sortDivs() {
document.querySelector("body div:last-child").remove();
alert('sorting now...')
let toSort = document.getElementsByTagName("div")
toSort = Array.prototype.slice.call(toSort, 0)
toSort.sort((a, b) => {
let aord = parseFloat(a.textContent);
let bord = parseFloat(b.textContent);
return bord - aord;
})
document.body.innerHTML = ""
for(var i = 0, l = toSort.length; i < l; i++) {
document.querySelector('body').appendChild(toSort[i]);
}
}
and in the css file set flex-wrap to wrap-reverse. Hope I could help :)
PS: please, implement some else if instead of doing only if
Here is a small fiddle with my sample code demonstrating a simple solution in pure JavaScript and absolute CSS positioning for what you are trying to achieve. Link
As some pointed out already, there might be a library, that already provides a better and complete solution for this - I did not research if it is so.
Code:
file.js
var container = document.getElementById("container")
var results = [1,2,3,4,5,6,7,8]
//you can pre-calculate the order of the distances
//here already orderdered array [distanec][X-axis][Y-axis]
var distances =[[0,0,0],
[1,1,0],
[1,0,1],
[1.414, 1,1],
[2,0,2],
[2,2,0],
[2.234, 2,1],
[2.234, 1,2]]
for (i = 0; i < results.length; i++){
var newDiv = document.createElement("div")
newDiv.className = "result"
newDiv.innerHTML = results[i]
newDiv.style.left = distances[i][1]*20 + "px"
newDiv.style.bottom = distances[i][2]*20 + "px"
container.appendChild(newDiv)
}
function setColor(element){
// set class based on value - you already have this part
}
style.css
#container {
border: 4px;
border-color: red;
border-style: solid;
height: 200px;
width: 200px;
position: relative;
}
.result{
border: 2px;
width: 20px;
height: 20px;
position: absolute;
border-color: blue;
border-style: solid;
text-align: center;
}
site.html
<div id="container">
</div>
Output:

Using mousedown/mouseup in js, not able to change the position of elements?

I have created a set of dots using div tags inside a div tag. My need is when I drag the last dot, the whole set of dots should move and sit where mouse pointer is placed at present. I tried achieving it using addeventlistner for mouse clicks but failed in my attempt.
Can someone point out the intuition in the segment below?
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
The immediate answer to your question is that dots.style.marginLeft needs to be equal to a string, containing the units.
Hence, this would work:
dots.style.marginLeft = ((currentMousePointerPos+latestMousePointerPos) + "px");
However:
Your mouseup listener only listens to the event that the mouse click is released when it's over the element, so it doesn't do much. If you assign the listener to the whole document, the listener's function would be activated no matter where the mouseup event occurres.
currentMousePointerPos + latestMousePointerPos doesn't represent the final position of the mouse.
If we fix these two issues the will code still operate weirdly, because the left side of the dots element is set to the mouse's last position.
Therefore we just need to subtract the element's width from the marginLeft property.
The following code combines everything I've mentioned:
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos;
// Notice how the listener is bound to the whole document
document.addEventListener("mouseup", function(event) {
currentMousePointerPos = event.pageX;
dots.style.marginLeft = ((currentMousePointerPos-dots.offsetWidth) + "px");
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
Is this what you are looking for?
You should use the mousemove event instead to detect any dragging
after mousedown.
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
window.addEventListener('mousemove', move, true);
/*var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})*/
}
});
window.addEventListener("mouseup", function(event) {
window.removeEventListener('mousemove', move, true);
});
function move(e){
var div = document.getElementsByClassName('dots')[0];
div.style.position = 'absolute';
div.style.top = -8+e.clientY + 'px';
div.style.left = -135+8+e.clientX + 'px';
}
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}

How can you intelligently break a block of text in to paragraphs?

The following fiddle allows text to be imported into a <textarea> and dynamically generated into equal paragraphs. Is it possible to break the text in to paragraphs without breaking the text in the middle of a sentence? I want the length of each paragraph to be at or near the ChunkSize or user-adjusted limit, with each paragraph's element on the page being the same height.
If an updated fiddle could please be provided, would be extremely helpful, as I am still new to coding.
Thank You!
Fiddle
$(function() {
$('select').on('change', function() {
//Lets target the parent element, instead of P. P will inherit it's font size (css)
var targets = $('#content'),
property = this.dataset.property;
targets.css(property, this.value);
sameheight('#content p');
}).prop('selectedIndex', 0);
});
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content');
chunkSize = 100;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
content.addEventListener('paste', handlePaste);
function initialDistribute() {
custom = parseInt(document.getElementById("custom").value);
chunkSize = (custom > 0) ? custom : chunkSize;
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.classList.add("Paragraph_CSS");
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
sameheight('#content p');
}
function handleKey(e) {
var para = e.target,
position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') {
return;
}
if (key != 13 && key != 8) {
redistributeAuto(para);
return;
}
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function handlePaste(e) {
if (e.target.tagName != 'P') {
return;
}
overflow = e.target.textContent + removeSiblings(fragment, true);
rearrange(remainingText);
}
function redistributeAuto(para) {
var text = para.textContent,
fullText;
if (text.length > chunkSize) {
fullText = removeSiblings(para, true);
}
rearrange(fullText);
}
function removeSiblings(elem, includeCurrent) {
var text = '',
next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [],
i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') {
boundary++;
}
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
#text_land {
border: 1px solid #ccc;
padding: 25px;
margin-bottom: 30px;
}
textarea {
width: 95%;
}
label {
display: block;
width: 50%;
clear: both;
margin: 0 0 .5em;
}
label select {
width: 50%;
float: right;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: monospace;
font-size: 1em;
}
h3 {
margin: 1.2em 0;
}
div {
margin: 1.2em;
}
textarea {
width: 100%;
}
button {
padding: .5em;
}
p {
/*Here the sliles for OTHER paragraphs*/
}
#content p {
font-size: inherit;
/*So it gets the font size set on the #content div*/
padding: 1.2em .5em;
margin: 1.4em 0;
border: 1px dashed #aaa;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h3>Import Text below, then press the button</h3>
<textarea id="textarea1" placeholder="Type text here, then press the button below." rows="5">
</textarea>
<input style="width:200px;" id="custom" placeholder="Custom Characters per box">
<br>
<button style="width:200px;" id="go">Divide Text into Paragraphs</button>
</div>
<div>
<h3 align="right">Divided Text Will Appear Below:</h3>
<hr>
<div id="content"></div>
</div>
You can take the approach of splitting the text in to sentences, and then adding sentences to the paragraphs until you reach the desired length (chunkSize in your code).
function splitText (text) {
var paragraph = "",
paragraphs = [],
sentenceRegex = /[^\.!\?]+([\.!\?]+|\s*$)/g,
sentences = text.match(sentenceRegex);
sentences.forEach(function createParagraphs (sentence, index) {
paragraph += sentence;
if (paragraph.length >= chunkSize || index === sentences.length - 1) {
paragraphs.push(paragraph);
paragraph = "";
}
});
return paragraphs.length === 0 ? [text] : paragraphs;
}
https://jsfiddle.net/DirectCtrl/95kuyw4g/4/ (Tried to keep the rest of the code as similar to what it was as possible).
This doesn't deal with margins (meaning you could potentially get much longer paragraphs if you have sentences which end near the boundaries or go well beyond the boundary limit), though those kinds of problems are very likely to appear regardless on edge cases (e.g. with a chunkSize of 100 characters, what do you do when the first sentence is 40 characters and the second is 160 characters?). Tweaking this to use a margin should be pretty trivial, though, if that is a requirement. As the number of characters per paragraph increases, this would become less of an issue.

Change font size dynamically on specific paragraph <p> elements?

The following JSFiddle splits texts into individual paragraph <p> boxes with a set text limit.
Is it possible to change the text size in the boxes according to the select option through contenteditable while still allowing to edit, removing the text limit and remaining the dynamic size property?
UPDATE 2: The boxes generated all need to be: "equal the same height and width" and change when the font is updated dynamically, needing to be consistent across all elements.
UPDATE 3: The last generated paragraph is not equal to the other paragraphs with border size height.
UPDATE 4: All paragraphs need to be generated equally with the height attribute auto. The problem with the current answers is the that the last generated paragraph border also needs to equal the same height as the previous height border as the other paragraphs, including when changing the font size.
Update 5 [image]: Example of Problem with last divided paragraph height and border not equal to others.
Link to Fiddle
If a new fiddle could be provided, it would be very much appreciated, as I am still new to coding.
Thank You!
$(function() {
$('select').on('change', function() {
var targets = $('p'),
property = this.dataset.property;
targets.css(property, this.value);
}).prop('selectedIndex', 0);
});
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content'),
chunkSize = 100;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
content.addEventListener('paste', handlePaste);
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
}
function handleKey(e) {
var para = e.target,
position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') {
return;
}
if (key != 13 && key != 8) {
redistributeAuto(para);
return;
}
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function handlePaste(e) {
if (e.target.tagName != 'P') {
return;
}
overflow = e.target.textContent + removeSiblings(fragment, true);
rearrange(remainingText);
}
function redistributeAuto(para) {
var text = para.textContent,
fullText;
if (text.length > chunkSize) {
fullText = removeSiblings(para, true);
}
rearrange(fullText);
}
function removeSiblings(elem, includeCurrent) {
var text = '',
next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [],
i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') {
boundary++;
}
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
#text_land {
border: 1px solid #ccc;
padding: 25px;
margin-bottom: 30px;
}
textarea {
width: 95%;
}
label {
display: block;
width: 50%;
clear: both;
margin: 0 0 .5em;
}
label select {
width: 50%;
float: right;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: monospace;
font-size: 1em;
}
h3 {
margin: 1.2em 0;
}
div {
margin: 1.2em;
}
textarea {
width: 100%;
}
button {
padding: .5em;
}
p {
padding: 1.2em .5em;
margin: 1.4em 0;
border: 1px dashed #aaa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="styles">
<label>Font-size:
<select data-property="font-size">
<option disabled>
Select font-size:
</option>
<option>
smaller
</option>
<option>
10px
</option>
<option>
12px
</option>
<option>
14px
</option>
<option>
16px
</option>
<option>
18px
</option>
<option>
20px
</option>
<option>
larger
</option>
</select>
</label>
</div>
<div>
<h3>Paste text in the field below to divide text into paragraphs..</h3>
<textarea id="textarea1" placeholder="Type text here, then press the button below." rows="5">
</textarea>
<br>
<br>
<button id="go">Divide Text into Paragraphs</button>
</div>
<div>
<h3 align="right">Divided Text Will Appear Below:</h3>
<hr>
<div id="content"></div>
</div>
Here you go JSFiddle
$('#FontSize').change(function(){
var fontsize = $(this).val();
$('textarea').css('fontSize',fontsize);
});
I'm not sure if I'm understanding the problem... You can set the font size to the parent element (#content{font-size:whatever}) and inherit it (#content p {font-size:inherit} ). If you set the font size in the parent if will apply to the the already added paragraphs AND the new ones when you add them.
(Changes in the fiddle: Selector in the change of the select, CSS selectors/properties for "p" and "#content p")
(Answer edited for "same height" paragraphs)
To get same height I added a sameheight(selector) function. I'm unsure about when you would like to trigger it, I've put it on select change and rearange(text). (Hope it helps. Fixed the height in function using outerHeight)
...and fiddle edited again: sameheight is also fired on window resize events.
function sameheight(selector){
var max_y=0;
var y=0;
$(selector).css('height','');
$(selector).each(function(){
y=$(this).height();
if(y>max_y) max_y=y;
});
$(selector).css('height',max_y);
}
$(function() {
$('select').on('change', function() {
//Lets target the parent element, instead of P. P will inherit it's font size (css)
var targets = $('#content'),
property = this.dataset.property;
targets.css(property, this.value);
sameheight('#content p');
}).prop('selectedIndex', 0);
});
$( window ).resize(function() {
sameheight('#content p');
});
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content'),
chunkSize = 100;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
content.addEventListener('paste', handlePaste);
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
sameheight('#content p');
}
function handleKey(e) {
var para = e.target,
position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') {
return;
}
if (key != 13 && key != 8) {
redistributeAuto(para);
return;
}
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function handlePaste(e) {
if (e.target.tagName != 'P') {
return;
}
overflow = e.target.textContent + removeSiblings(fragment, true);
rearrange(remainingText);
}
function redistributeAuto(para) {
var text = para.textContent,
fullText;
if (text.length > chunkSize) {
fullText = removeSiblings(para, true);
}
rearrange(fullText);
}
function removeSiblings(elem, includeCurrent) {
var text = '',
next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [],
i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') {
boundary++;
}
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
function sameheight(selector){
var max_y=0;
var y=0;
$(selector).css('height','');
$(selector).each(function(){
y=$(this).outerHeight();
if(y>max_y) max_y=y;
});
$(selector).css('height',max_y);
}
#text_land {
border: 1px solid #ccc;
padding: 25px;
margin-bottom: 30px;
}
textarea {
width: 95%;
}
label {
display: block;
width: 50%;
clear: both;
margin: 0 0 .5em;
}
label select {
width: 50%;
float: right;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: monospace;
font-size: 1em;
}
h3 {
margin: 1.2em 0;
}
div {
margin: 1.2em;
}
textarea {
width: 100%;
}
button {
padding: .5em;
}
p {
/*Here the sliles for OTHER paragraphs*/
}
#content p {
font-size:inherit;/*So it gets the font size set on the #content div*/
padding: 1.2em .5em;
margin: 1.4em 0;
border: 1px dashed #aaa;
overflow:hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="styles">
<label>Font-size:
<select data-property="font-size">
<option disabled>
Select font-size:
</option>
<option>
smaller
</option>
<option>
10px
</option>
<option>
12px
</option>
<option>
14px
</option>
<option>
16px
</option>
<option>
18px
</option>
<option>
20px
</option>
<option>
larger
</option>
</select>
</label>
</div>
<div>
<h3>Paste text in the field below to divide text into paragraphs..</h3>
<textarea id="textarea1" placeholder="Type text here, then press the button below." rows="5">
</textarea>
<br>
<br>
<button id="go">Divide Text into Paragraphs</button>
</div>
<div>
<h3 align="right">Divided Text Will Appear Below:</h3>
<hr>
<div id="content"></div>
</div>
Try like this
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="styles">
<label>Font-size: <select data-property="font-size"
onchange="$('#textarea1').css('font-size',this.value)">
<option disabled>Select font-size:</option>
<option>smaller</option>
<option>10px</option>
<option>12px</option>
<option>14px</option>
<option>16px</option>
<option>18px</option>
<option>20px</option>
<option>larger</option>
</select></label>
</div>
<div>
<h3>Paste text in the field below to divide text into paragraphs..</h3>
<textarea id="textarea1" placeholder=
"Type text here, then press the button below." rows="5"> Test text
</textarea><br>
<br>
<button id="go">Divide Text into Paragraphs</button>
</div>
I believe i have the answer for you:
$(function() {
$('select').on('change', function() {
var targets = $('p'),
property = this.dataset.property;
$("#content").css({'font-size': this.value});
}).prop('selectedIndex', 0);
});
I changed the function in order to set the font-size to the div rather that the paragraph. Is this what you wanted? As it is what i gathered from the info provided.
https://jsfiddle.net/n9b6wju8/14/
You can inject a dynamic style element into the DOM and update it with the font size you're after. You could use an MV* framework to update its content.
$('#FontSize').change(function(){
var fontsize = $(this).val();
$('#style').remove(); //yes, there are better ways to update its content
$('head').append('<style id="style">#content { font-size: '+fontsize + '}</style>');
});
Fiddled: https://jsfiddle.net/ovfiddle/m75gre8o/1/
This should help:
$('#FontSize').change(function(){
var fontsize = $(this).val();
$('textarea').css('fontSize',fontsize);
});
To change of font size of the div and text area:
$(function() {
$('select').change(function() {
var fontsize = $(this).val();
$('#textarea1').css('fontSize',fontsize);
$('#content').css('fontSize',fontsize);
}).prop('selectedIndex', 0);
});
to keep the height of your text area: in CSS
textarea {
width: 95%;
height: 200px;
}
use px aslo instead em
I have created a fork of your fiddle and have edited(added) the event handler of select. I have added some code in their which maintains the font-size of editable p elements. Using that reference you can then apply all your styles as per your requirement.
Don't forget to mark it right and upvote if you find my answer right.
JS Fiddle Fork
targets.parent().find('style').remove();
$('<style>[contenteditable="true"]
{'+property+':'+this.value+'}\n[contenteditable="true"]:focus{'+property+':'+this.value+'}</style>').prependTo(targets.parent());
{'+property+':'+this.value+'}[contenteditable="true"]:focus{'+property+':'+this.value+'}</style>');
Try this
$('#FontSize').change(function(){
var fontsize = $(this).val();
$('textarea, #content p').css('fontSize',fontsize);
});
$('#go').click(function(){
var fontsize = $('#FontSize').val();
$('#content').css('fontSize',fontsize);
});
$(function() {
$('select').on('change', function() {
var targets = $('p'),
property = this.dataset.property;
targets.css(property, this.value);
}).prop('selectedIndex', 0);
});
var btn = document.getElementById('go'),
textarea = document.getElementById('textarea1'),
content = document.getElementById('content'),
chunkSize = 400;
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);
content.addEventListener('paste', handlePaste);
function initialDistribute() {
var text = textarea.value;
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
rearrange(text);
}
function rearrange(text) {
var chunks = splitText(text, false);
chunks.forEach(function(str, idx) {
para = document.createElement('P');
para.setAttribute('contenteditable', true);
para.textContent = str;
content.appendChild(para);
});
}
function handleKey(e) {
var para = e.target,
position,
key, fragment, overflow, remainingText;
key = e.which || e.keyCode || 0;
if (para.tagName != 'P') {
return;
}
if (key != 13 && key != 8) {
redistributeAuto(para);
return;
}
position = window.getSelection().getRangeAt(0).startOffset;
if (key == 13) {
fragment = para.lastChild;
overflow = fragment.textContent;
fragment.parentNode.removeChild(fragment);
remainingText = overflow + removeSiblings(para, false);
rearrange(remainingText);
}
if (key == 8 && para.previousElementSibling && position == 0) {
fragment = para.previousElementSibling;
remainingText = removeSiblings(fragment, true);
rearrange(remainingText);
}
}
function handlePaste(e) {
if (e.target.tagName != 'P') {
return;
}
overflow = e.target.textContent + removeSiblings(fragment, true);
rearrange(remainingText);
}
function redistributeAuto(para) {
var text = para.textContent,
fullText;
if (text.length > chunkSize) {
fullText = removeSiblings(para, true);
}
rearrange(fullText);
}
function removeSiblings(elem, includeCurrent) {
var text = '',
next;
if (includeCurrent && !elem.previousElementSibling) {
parent = elem.parentNode;
text = parent.textContent;
while (parent.hasChildNodes()) {
parent.removeChild(parent.lastChild);
}
} else {
elem = includeCurrent ? elem.previousElementSibling : elem;
while (next = elem.nextSibling) {
text += next.textContent;
elem.parentNode.removeChild(next);
}
}
return text;
}
function splitText(text, useRegex) {
var chunks = [],
i, textSize, boundary = 0;
if (useRegex) {
var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
chunks = text.match(regex) || [];
} else {
for (i = 0, textSize = text.length; i < textSize; i = boundary) {
boundary = i + chunkSize;
if (boundary <= textSize && text.charAt(boundary) == ' ') {
chunks.push(text.substring(i, boundary));
} else {
while (boundary <= textSize && text.charAt(boundary) != ' ') {
boundary++;
}
chunks.push(text.substring(i, boundary));
}
}
}
return chunks;
}
#text_land {
border: 1px solid #ccc;
padding: 25px;
margin-bottom: 30px;
}
textarea {
width: 95%;
height: 100px;
}
label {
display: block;
width: 50%;
clear: both;
margin: 0 0 .5em;
}
label select {
width: 50%;
float: right;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: monospace;
font-size: 1em;
}
h3 {
margin: 1.2em 0;
}
div {
margin: 1.2em;
}
textarea {
width: 100%;
}
button {
padding: .5em;
}
p {
padding: 1.2em .5em;
margin: 1.4em 0;
width: 200px;
height: 200px;
border: 1px dashed #aaa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div id="styles">
<label>Font-size:
<select id="FontSize" data-property="font-size">
<option disabled>
Select font-size:
</option>
<option>
smaller
</option>
<option>
10px
</option>
<option>
12px
</option>
<option>
14px
</option>
<option>
16px
</option>
<option>
18px
</option>
<option>
20px
</option>
<option>
larger
</option>
</select></label>
</div>
<div>
<h3>Paste text in the field below to divide text into paragraphs..</h3>
<textarea id="textarea1" placeholder=
"Type text here, then press the button below." rows="5">
</textarea><br>
<br>
<button id="go">Divide Text into Paragraphs</button>
</div>
<div>
<h3 align="right">Divided Text Will Appear Below:</h3>
<hr>
<div id="content"></div>
</div>

Categories

Resources