Here’s a suggestion for style, I’ll leave the final result here at the beginning:
var span = document.getElementsByTagName('span')[0];
function getCaretPosition(element) {
var caretOffset = 0;
if (typeof window.getSelection != "undefined") {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
var textRange = document.selection.createRange();
var preCaretTextRange = document.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
function setCaret(el, pos) {
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType == 3) {
if (node.length >= pos) {
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1;
} else {
pos -= node.length;
}
} else {
pos = setCaret(node, pos);
if (pos == -1) {
return -1;
}
}
}
return pos;
}
function detectIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
var trident = ua.indexOf('Trident/');
if (trident > 0) {
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (edge > 0) return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
return false;
}
var caretPositions = [];
var contents = [];
var maxLength = 10;
var event;
var div = document.getElementsByTagName('div')[0];
if(detectIE() == false){
event = "input"
}else{
event = "keyup";
}
div.addEventListener(event, insertMark);
function insertMark(e) {
var self = e.target;
var content = self.textContent;
caretPositions.push(getCaretPosition(this));
contents.push(content);
if (contents[contents.length - 1] != contents[contents.length - 2]) {
if (content.length > maxLength) {
self.innerHTML = content.substring(0, maxLength) + "<span class='marker'>" + content.substring(maxLength, content.length) + "</span>";
} else {
self.innerHTML = content;
}
setCaret(self, (caretPositions[caretPositions.length - 1]));
}
}
div.addEventListener('click', function() {
caretPositions.push(getCaretPosition(this));
})
div {
box-sizing: border-box;
width: 100%;
height: 100px;
background: #fff;
border: 1px solid #666;
padding: 7px;
line-height: 20px;
border-radius: 3px;
}
div span {
background: rgba(230, 0, 0, 0.5);
border-radius: 3px;
line-height: 20px;
}
<div contenteditable></div>
Footsteps:
- Check the amount of characters ignoring tags
- Create a
<span>
with red background to put what you have after 10 chars inside it
- Assign concatenation to the event
keyup
if the user is using IE, and input
otherwise
- Capture the position of Caret and "set it" so that at the time of the replace the pipe don’t move
The first two steps are very simple so I won’t focus too much on them.
Function to capture the position of the Caret in a div contenteditable
:
function getCaretPosition(element) {
var caretOffset = 0;
if (typeof window.getSelection != "undefined") {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
var textRange = document.selection.createRange();
var preCaretTextRange = document.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
Addressable in: https://stackoverflow.com/questions/16736680/get-caret-position-in-contenteditable-div-including-tags
Function setCaret:
function setCaret(el, pos) {
for (var node of el.childNodes) {
if (node.nodeType == 3) {
if (node.length >= pos) {
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1;
} else {
pos -= node.length;
}
} else {
pos = setCaret(node, pos);
if (pos == -1) {
return -1;
}
}
}
return pos;
}
Available in: https://stackoverflow.com/questions/36869503/set-caret-position-in-contenteditable-div-that-has-children
Function to recognize IE:
function detectIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
var trident = ua.indexOf('Trident/');
if (trident > 0) {
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (edge > 0) return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
return false;
}
Including the <span>
:
var caretPositions = []; // array com as posições dos cursores
var contentLengths = []; // array com as conteudos da tag
var maxLength = 10; // máximo de caracteres
var event; // receberá o evento
var div = document.getElementsByTagName('div')[0]; // div contenteditable
if(detectIE() == false){
event = "input" // se não estiver usando o IE usar "input"
}else{
event = "keyup"; // se estiver usando o IE usar "keyup"
}
div.addEventListener(event, insertMark); // atribui a adição ao evento keyup ou input
function insertMark(e) {
var self = e.target; // captura o elemento em questão
var content = self.textContent; // captura somente o text escrito na div, sem tags
caretPositions.push(getCaretPosition(this));
contents.push(content); // adiciona ao array o conteúdo da tag a cada adição
// só executa o bloco de código de houver tido alguma mudança no tag (ignorando navegação com as setas, por exemplo)
if (contents[contents.length - 1] != contents[contents.length - 2]) {
if (content.length > maxLength) {
// seleciona do primeiro ao 10º char e concatena com uma <span> com estilo predefinido que irá conter do 10º ao último char
self.innerHTML = content.substring(0, maxLength) + "<span class='marker'>" + content.substring(maxLength, content.length) + "</span>";
} else {
self.innerHTML = content; // retira a marcação
}
// sempre que houver a mudança no conteúdo da div, o curso irá pro início do text, e como não se quer isso:
setCaret(self, (caretPositions[caretPositions.length - 1])); // colocar o cursor no mesmo lugar de antes
}
}
div.addEventListener('click', function() {
caretPositions.push(getCaretPosition(this)); // adiciona a posição do curso ao array definido a cada click
})
Obs:
- I’m using the
textContent
to capture the content, but there is also the option of innerText
, see the difference here
- I use a function to check if the user is in IE, the only difference is a small delay in the marked rendering, since IE does not support
input
in a div contenteditable
It would be nice to post what you tried and what didn’t work. The way it is, the question is somewhat broad, since you can not know where exactly is your difficulty.
– Bacco
@Bacco summarized the question.
– Elaine