在textarea的Mimicng插入符号

时间:2013-07-08 12:08:03

标签: javascript textarea richtextbox caret

我试图模仿textarea的插入符号,目的是创建一个非常轻量级的丰富文本区域。我不想使用像codemirror或任何其他大型库那样的东西,因为我不会使用它们的任何功能。

我有一个<pre>位于具有透明背景的textarea后面,因此我可以模拟文本中的突出显示效果。但是,我也希望能够更改字体颜色(因此它并不总是相同)。所以我在textarea上尝试color: transparent,它允许我以我想要的任何方式设置文本样式,因为它只出现在textarea后面的<pre>元素上,但插入符号消失了。

我已经让它工作得相当好,虽然它并不完美。主要的问题是当你按下一个键并且垃圾邮件那个角色时,插入符似乎总是落后于一个角色。不仅如此,它似乎相当资源很重。

如果您在代码中看到需要改进的任何其他内容,请随时对此发表评论!

以下是代码的小提琴:http://jsfiddle.net/2t5pu/25/

对于那些因任何原因不想访问jsfiddle的人来说,这是完整的代码:

CSS:

textarea, #fake_area {
    position: absolute;
    margin: 0;
    padding: 0;
    height: 400px;
    width: 600px;
    font-size: 16px;
    font: 16px "Courier New", Courier, monospace;
    white-space: pre;
    top: 0;
    left: 0;
    resize: none;
    outline: 0;
    border: 1px solid orange;
    overflow: hidden;
    word-break: break-word;
    padding: 5px;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -ms-box-sizing: border-box;
    box-sizing: border-box;
}
#fake_area {
    /* hide */
    opacity: 0;
}
#caret {
    width: 1px;
    height: 18px;
    position: absolute;
    background: #f00;
    z-index: 100;
}

HTML:

<div id="fake_area"><span></span></div>
<div id="caret"></div>
<textarea id="textarea">test</textarea>

JAVASCRIPT:

var fake_area = document.getElementById("fake_area").firstChild;
var fake_caret = document.getElementById("caret");
var real_area = document.getElementById("textarea");

$("#textarea").on("input keydown keyup propertychange click", function () {
    // Fill the clone with textarea content from start to the position of the caret. 
    // The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
doStuff();    
});

var timeout;
function doStuff() {
    if(timeout) clearTimeout(timeout);
    timeout=setTimeout(function() {
        fake_area.innerHTML = real_area.value.substring(0, getCaretPosition(real_area)).replace(/\n$/, '\n\u0001');
    setCaretXY(fake_area, real_area, fake_caret, getPos("textarea"));
    }, 10);
}


    function getCaretPosition(el) {
        if (el.selectionStart) return el.selectionStart;
        else if (document.selection) {
            //el.focus();
            var r = document.selection.createRange();
            if (r == null) return 0;

            var re = el.createTextRange(), rc = re.duplicate();
            re.moveToBookmark(r.getBookmark());
            rc.setEndPoint('EndToStart', re);

            return rc.text.length;
        }
        return 0;
    }

    function setCaretXY(elem, real_element, caret, offset) {
        var rects = elem.getClientRects();
        var lastRect = rects[rects.length - 1];

        var x = lastRect.left + lastRect.width - offset[0] + document.body.scrollLeft,
            y = lastRect.top - real_element.scrollTop - offset[1] + document.body.scrollTop;

        caret.style.cssText = "top: " + y + "px; left: " + x + "px";
        //console.log(x, y, offset);
    }

    function getPos(e) {
        e = document.getElementById(e);
        var x = 0;
        var y = 0;
        while (e.offsetParent !== null){
            x += e.offsetLeft;
            y += e.offsetTop;
            e = e.offsetParent;
        }
        return [x, y];
    }

提前致谢!

3 个答案:

答案 0 :(得分:4)

可编辑的Div元素不能解决整个问题吗?

突出显示的代码:

http://jsfiddle.net/masbicudo/XYGgz/3/

var prevText = "";
var isHighlighting = false;
$("#textarea").bind("paste drop keypress input textInput DOMNodeInserted", function (e){
    if (!isHighlighting)
    {
        var currentText = $(this).text();
        if (currentText != prevText)
        {
            doSave();
            isHighlighting = true;
            $(this).html(currentText
                   .replace(/\bcolored\b/g, "<font color=\"red\">colored</font>")
                   .replace(/\bhighlighting\b/g, "<span style=\"background-color: yellow\">highlighting</span>"));
            isHighlighting = false;
            prevText = currentText;
            doRestore();
        }
    }
});

不幸的是,这使得一些编辑功能丢失了,比如 Ctrl + Z ...当粘贴文本时,插入符号保留在粘贴文本的开头。

我已将其他答案的代码组合在一起以生成此代码,所以请给予他们信任。

编辑:我发现了一些有趣的内容...如果您使用的是contentEditable元素,则会显示本地插入符号,并且在其中使用另一个带有隐藏字体的元素:

<div id="textarea" contenteditable style="color: red"><div style="color: transparent; background-color: transparent;">This is some hidden text.</div></div>

http://jsfiddle.net/masbicudo/qsRdg/4/

答案 1 :(得分:2)

我认为滞后是因为键盘触发doStuff有点太晚了,但是keydown有点太快了。

尝试使用此代替jQuery事件连接(通常我更喜欢事件进行轮询,但在这种情况下,它可能会给出更好的感觉)...

setInterval(function () { doStuff(); }, 10); // 100 checks per second

function doStuff() {
    var newHTML = real_area.value.substring(0, getCaretPosition(real_area)).replace(/\n$/, '\n\u0001');
    if (fake_area.innerHTML != newHTML) {
        fake_area.innerHTML = newHTML;
        setCaretXY(fake_area, real_area, fake_caret,                         getPos("textarea"));
    }
}

...或者这里的小提琴: http://jsfiddle.net/2t5pu/27/

答案 2 :(得分:2)

这似乎很有效,并且不会使用任何民意调查,就像我在评论中谈到的那样。

var timer=0;
$("#textarea").on("input keydown keyup propertychange click paste cut copy mousedown mouseup change", function () {
    clearTimeout(timer);
    timer=setTimeout(update, 10);    
});

http://jsfiddle.net/2t5pu/29/

也许我错过了一些东西,但我认为这是非常可靠的,并且它比使用间隔创建自己的事件更好。

编辑:添加了一个计时器以防止堆叠。