// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea var clone, getCaret; getCaret = function(el) { var r, rc, re; if (el.selectionStart) { return el.selectionStart; } else if (document.selection) { el.focus(); r = document.selection.createRange(); if (!r) return 0; re = el.createTextRange(); rc = re.duplicate(); re.moveToBookmark(r.getBookmark()); rc.setEndPoint("EndToStart", re); return rc.text.length; } return 0; }; clone = null; $.fn.caret = function(){ return getCaret(this[0]); }; /** This is a jQuery plugin to retrieve the caret position in a textarea @module $.fn.caretPosition **/ $.fn.caretPosition = function(options) { var after, before, getStyles, guard, html, important, insertSpaceAfterBefore, letter, makeCursor, p, pPos, pos, span, styles, textarea, val; if (clone) { clone.remove(); } span = $("#pos span"); textarea = $(this); getStyles = function(el) { if (el.currentStyle) { return el.currentStyle; } else { return document.defaultView.getComputedStyle(el, ""); } }; styles = getStyles(textarea[0]); clone = $("<div><p></p></div>").appendTo("body"); p = clone.find("p"); clone.width(textarea.width()); clone.height(textarea.height()); important = function(prop) { return styles.getPropertyValue(prop); }; clone.css({ border: "1px solid black", padding: important("padding"), resize: important("resize"), "max-height": textarea.height() + "px", "overflow-y": "auto", "word-wrap": "break-word", position: "absolute", left: "-7000px" }); p.css({ margin: 0, padding: 0, "word-wrap": "break-word", "letter-spacing": important("letter-spacing"), "font-family": important("font-family"), "font-size": important("font-size"), "line-height": important("line-height") }); pos = options && (options.pos || options.pos === 0) ? options.pos : getCaret(textarea[0]); val = textarea.val().replace("\r", ""); if (options && options.key) { val = val.substring(0, pos) + options.key + val.substring(pos); } before = pos - 1; after = pos; insertSpaceAfterBefore = false; // if before and after are \n insert a space if (val[before] === "\n" && val[after] === "\n") { insertSpaceAfterBefore = true; } guard = function(v) { var buf; buf = v.replace(/</g, "<"); buf = buf.replace(/>/g, ">"); buf = buf.replace(/[ ]/g, "​ ​"); return buf.replace(/\n/g, "<br />"); }; makeCursor = function(pos, klass, color) { var l; l = val.substring(pos, pos + 1); if (l === "\n") return "<br>"; return "<span class='" + klass + "' style='background-color:" + color + "; margin:0; padding: 0'>" + guard(l) + "</span>"; }; html = ""; if (before >= 0) { html += guard(val.substring(0, pos - 1)) + makeCursor(before, "before", "#d0ffff"); if (insertSpaceAfterBefore) { html += makeCursor(0, "post-before", "#d0ffff"); } } if (after >= 0) { html += makeCursor(after, "after", "#ffd0ff"); if (after - 1 < val.length) { html += guard(val.substring(after + 1)); } } p.html(html); clone.scrollTop(textarea.scrollTop()); letter = p.find("span:first"); pos = letter.offset(); if (letter.hasClass("before")) { pos.left = pos.left + letter.width(); } pPos = p.offset(); return { left: pos.left - pPos.left, top: (pos.top - pPos.top) - clone.scrollTop() }; };