I am making a text editor, and I want to have a feature, such that while typing, if the user enters some keyword (e.g. happy, sad), the word is automaticly styled (e.g. color changed). How might I go about doing this?
document.getElementById('texteditor').addEventListener('keyup', function(e) {
styleCode(); //Style the text input
});
//Change the result of pressing Enter and Tab keys
document.getElementById('texteditor').addEventListener('keydown', function(e) {
switch (e.key) {
case 'Tab':
e.preventDefault();
document.execCommand('insertHTML', false, ' '); //Insert a 4-space tab
break;
case 'Enter':
e.preventDefault();
document.execCommand("insertLineBreak"); //Insert a new line
break;
}
});
function styleCode(){
//Style the code in the input box
}
#texteditor {
border: 3px solid black;
width:100%;
height: 500px;;
overflow:auto;
flex:1;
word-wrap: break-word;
word-break: break-all;
white-space:pre-wrap;
padding:5px;
font-family: Consolas,"courier new";
font-size:14px;
}
.styleA {
color:red;
}
.styleB {
color:blue;
}
<div id='texteditor' contenteditable></div>
Basically, when the user fully types "happy" (upon releasing the 'y' key) the word "happy" should turn red (using the styleA CSS class) in the editor. A similar thing should happen when the user finishes typing "sad"; the word "sad" should turn blue using the styleB CSS class.
Thanks in advance for any help.
const SpecialWords = [
"happy",
"sad"//style word
];
const WordColors = [
"styleA",
"styleB"//style class name
];
document.getElementById('texteditor').addEventListener('keyup', function(e) {
styleCode(); //Style the text input
});
//Change the result of pressing Enter and Tab keys
document.getElementById('texteditor').addEventListener('keydown', function(e) {
switch (e.key) {
case 'Tab':
e.preventDefault();
document.execCommand('insertHTML', false, ' '); //Insert a 4-space tab
break;
case 'Enter':
e.preventDefault();
document.execCommand("insertLineBreak"); //Insert a new line
break;
}
});
var oldWord = "";//initialise
function styleCode() {
//Style the code in the input box
var wordList = document.getElementById('texteditor').innerText.split(" ");
/*if old word is same as now then it means we have presed arrow key or caps or so,it do not wan't to style now as no change*/
if(!(oldWord == document.getElementById('texteditor').innerText)){
var oldPos = getCaretPosition(document.getElementById('texteditor'));//backup old position of cursor
for (let n = 0; n < SpecialWords.length; n++) {
var res = replaceAll(wordList,SpecialWords[n],`<span class="${WordColors[n]}">${SpecialWords[n]}</span>`).join(" ");//style adding
}
document.getElementById('texteditorS').innerHTML=res;
setCursor(oldPos,document.getElementById('texteditor'));//set back cursor position
}
oldWord = document.getElementById('texteditor').innerText;//old word for next time's reference
}
function replaceAll(array, find, replace) {
var arr = array;
for (let i = 0; i < arr.length; i++) {
if (arr[i] == find)
arr[i] = replace;
}
return (arr);
}
function getCaretPosition(editableDiv) {
var caretPos = 0,
sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
function setCursor(pos,editableDiv) {
if(!(pos == 0)){//if 0 it gives unwanted error
var tag = editableDiv;
// Creates range object
var setpos = document.createRange();
// Creates object for selection
var set = window.getSelection();
// Set start position of range
setpos.setStart(tag.childNodes[0], pos);
// Collapse range within its boundary points
// Returns boolean
setpos.collapse(true);
// Remove all ranges set
set.removeAllRanges();
// Add range with respect to range object.
set.addRange(setpos);
// Set cursor on focus
tag.focus();
}
}
.edit {
border: 3px solid black;
width: 100%;
height: 500px;
;
overflow: auto;
flex: 1;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
padding: 5px;
font-family: Consolas, "courier new";
font-size: 14px;
}
.styleA {
color: red;
}
.styleB {
color: blue;
}
#texteditorS{
pointer-events:none; /*click through*/
position:relative;
bottom: calc(500px + 2 * (5px + 3px));/*overlay on top of editable div,500px is height,5px is padding,3px is border*/
}
<div id='texteditor' class="edit" contenteditable></div>
<div id='texteditorS' class="edit"></div>
Features
support selection( ctrl-a ), arrow keys ...
many word,style support - just define variable
scrollable higlight
highly commented code
Idea
use another editor named texteditorS for style ,it overlaps the main editor,have click-through support for mouse to click the below/underlying editor
check whether any change in words has occured as it might be press of ctrl-a or arrow keys
sync-scroll of texteditor to texteditorS for scrolling styles
save the cursor position and after setting innerHTML set back cursor position.
Related
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.
I have this code:
<div id="ce" contenteditable>I love me some foo bar and shit.</div>
<p>
clear
</p>
<script>
var changed, lastValue = '', div = $('#ce'), words = [ 'oele', 'geel',
'politie', 'foo bar' ];
function markWords() {
var html = div.html().replace(/<\/?strong>/gi, ''), text = html
.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' '), exp;
$.each(words, function(i, word) {
exp = new RegExp('\\b(' + word + ')\\b', 'gi');
html = html.replace(exp, function(m) {
console.log('WORD MATCH:', m);
return '<strong>' + m + '</strong>';
});
});
//html = html.replace(' ', ' ').replace(/\s+/g, ' ');
console.log('HTML:', html);
console.log('----');
div.html(html);
}
setInterval(function() {
var html = div.html();
if (lastValue != html && html) {
//console.log(lastValue);
//console.log(html);
//console.log('----');
lastValue = html;
markWords();
setEndOfContenteditable(div[0]);
}
}, 500);
function setEndOfContenteditable(contentEditableElement) {
var range, selection;
if (document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
} else if (document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
}
}
</script>
<style>
[contenteditable] {
padding: 10px;
border: dotted 1px #aaa;
}
[contenteditable]>div {
margin: 10px 0;
}
[contenteditable] strong {
font-weight: normal;
background: red;
color: white;
}
</style>
http://jsfiddle.net/nebgcpzz/
What it does is that it gets specific words and puts red background on them. But when you put cursor in middle or anywhere (but the end) and type 1 character, cursors jumps forward. Any taughts?
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.
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();
}
}
I the following code here in which you can play a Wheel of Fortune-like game with one person (more of my test of javascript objects).
My issue is that when the screen is small enough, the lines do not seem to break correctly.
For example:
Where the circle is, I have a "blank" square. The reason why I have a blank square is so that when the screen is big enough, the square serves as a space between the words.
Is there a way in my code to efficiently know if the blank square is at the end of the line and to not show it, and then the window gets resized, to show it accordingly?
The only thought I had was to add a window.onresize event which would measure how big the words are related to how big the playing space is and decide based on that fact, but that seems very inefficient.
This is my code for creating the game board (starts # line 266 in my fiddle):
WheelGame.prototype.startRound = function (round) {
this.round = round;
this.lettersInPuzzle = [];
this.guessedArray = [];
this.puzzleSolved = false;
this.currentPuzzle = this.puzzles[this.round].toUpperCase();
this.currentPuzzleArray = this.currentPuzzle.split("");
var currentPuzzleArray = this.currentPuzzleArray;
var lettersInPuzzle = this.lettersInPuzzle;
var word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
for (var i = 0; i < currentPuzzleArray.length; ++i) {
var span = document.createElement('div');
span.className = "wordLetter ";
if (currentPuzzleArray[i] != " ") {
span.className += "letter";
if (!(currentPuzzleArray[i] in lettersInPuzzle.toObject())) {
lettersInPuzzle.push(currentPuzzleArray[i]);
}
word.appendChild(span);
} else {
span.className += "space";
word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
word.appendChild(span);
word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
}
span.id = "letter" + i;
}
var clear = document.createElement('div');
displayArea.appendChild(clear);
clear.className = "clear";
};
Instead of JavaScript, this sounds more like a job for CSS, which solves this problem all the time when dealing with centered text.
Consider something like this:
CSS
#board {
text-align: center;
border: 1px solid blue;
font-size: 60pt;
}
.word {
display: inline-block;
white-space: nowrap; /* Don't break up words */
margin: 0 50px; /* The space between words */
}
.word span {
display: inline-block;
width: 100px;
border: 1px solid black
}
HTML
<div id="board">
<span class="word"><span>W</span><span>h</span><span>e</span><span>e</span><span>l</span></span>
<span class="word"><span>o</span><span>f</span></span>
<span class="word"><span>F</span><span>o</span><span>r</span><span>t</span><span>u</span><span>n</span><span>e</span></span>
</div>
Here's a fiddle (try resizing the output pane).
Here you go. Uses the element.offsetTop to determine if a .space element is on the same line as its parent.previousSibling.lastChild or parent.nextSibling.firstChild.
Relevant Code
Note: In the fiddle I change the background colors instead of changing display so you can see it work.
// hides and shows spaces if they are at the edge of a line or not.
function showHideSpaces() {
var space,
spaces = document.getElementsByClassName('space');
for (var i = 0, il = spaces.length ; i < il; i++) {
space = spaces[i];
// if still display:none, then offsetTop always 0.
space.style.display = 'inline-block';
if (getTop(nextLetter(space)) != space.offsetTop || getTop(prevLetter(space)) != space.offsetTop) {
space.style.display = 'none';
} else {
space.style.display = 'inline-block';
}
}
}
// navigate to previous letter
function nextLetter(fromLetter) {
if (fromLetter.nextSibling) return fromLetter.nextSibling;
if (fromLetter.parentElement.nextSibling)
return fromLetter.parentElement.nextSibling.firstChild;
return null;
}
// navigate to next letter
function prevLetter(fromLetter) {
if (fromLetter.previousSibling) return fromLetter.previousSibling;
if (fromLetter.parentElement.previousSibling)
return fromLetter.parentElement.previousSibling.lastChild;
return null;
}
// get offsetTop
function getTop(element) {
return (element) ? element.offsetTop : 0;
}
showHideSpaces();
if (window.addEventListener) window.addEventListener('resize', showHideSpaces);
else if (window.attachEvent) window.attachEvent('onresize', showHideSpaces);
jsFiddle