在Textarea中的光标位置显示DIV

时间:2008-09-24 16:55:29

标签: javascript dom

对于我的项目,我很乐意为特定的textarea提供自动完成功能。类似于intellisense / omnicomplete的工作原理。然而,我必须找出绝对光标位置,以便我知道DIV应该出现在哪里。

事实证明:那是(几乎我希望)无法实现的。有没有人有一些巧妙的想法如何解决这个问题?

11 个答案:

答案 0 :(得分:35)

答案 1 :(得分:4)

我在俄罗斯JavaScript网站上发布了与此问题相关的主题。

如果您不理解俄语试用版Google翻译:http://translate.google.ru/translate?js=y&prev=_t&hl=ru&ie=UTF-8&layout=1&eotf=1&u=http://javascript.ru/forum/events/7771-poluchit-koordinaty-kursora-v-tekstovom-pole-v-pikselyakh.html&sl=ru&tl=en

在翻译版本的代码示例中存在一些标记问题,因此您可以read the code in the original Russian post

这个想法很简单。没有简单,通用和跨浏览器的方法来获取像素中的光标位置。坦率地说有,但仅适用于Internet Explorer。

在其他浏览器中,如果你真的需要计算它,你必须......

  • 创建一个不可见的DIV
  • 将文本框的所有样式和内容复制到该DIV
  • 然后将HTML元素插入文本框中文本框中
  • 的文本中完全相同的位置
  • 获取该HTML元素的坐标

答案 2 :(得分:4)

我不会再解释与此相关的问题,因为在其他帖子中对它们进行了很好的解释。只是指出一个可能的解决方案,它有一些错误但它是一个起点。

幸运的是,Github上有一个脚本来计算相对于它的容器的插入位置,但它需要jQuery。 GitHub页面:jquery-caret-position-getter, Thanxs to Bevis.Zhao。

基于此,我实施了下一个代码:检查它的实际行动here in jsFiddle.net

<html><head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>- jsFiddle demo by mjerez</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script>
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/normalize.css">
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/result-light.css">   
    <script type="text/javascript" src="https://raw.github.com/beviz/jquery-caret-position-getter/master/jquery.caretposition.js"></script>     
    <style type="text/css">
        body{position:relative;font:normal 100% Verdana, Geneva, sans-serif;padding:10px;}
        .aux{background:#ccc;opacity: 0.5;width:50%;padding:5px;border:solid 1px #aaa;}
        .hidden{display:none}
        .show{display:block; position:absolute; top:0px; left:0px;}
    </style>
    <script type="text/javascript">//<![CDATA[ 
    $(document).keypress(function(e) {
        if ($(e.target).is('input, textarea')) {
            var key = String.fromCharCode(e.which);
            var ctrl = e.ctrlKey;
            if (ctrl) {
                var display = $("#autocomplete");
                var editArea = $('#editArea');            
                var pos = editArea.getCaretPosition();
                var offset = editArea.offset();
                // now you can use left, top(they are relative position)
                display.css({
                    left: offset.left + pos.left,
                    top:  offset.top + pos.top,
                    color : "#449"
                })
                display.toggleClass("show");
                return false;
            }
        }

    });
    window.onload = (function() {
        $("#editArea").blur(function() {
            if ($("#autocomplete").hasClass("show")) $("#autocomplete").toggleClass("show");
        })
    });
    //]]>  
    </script>
</head>
<body>
    <p>Click ctrl+space to while you write to diplay the autocmplete pannel.</p>
    </br>
    <textarea id="editArea" rows="4" cols="50"></textarea>
    </br>
    </br>
    </br>
    <div id="autocomplete" class="aux hidden ">
        <ol>
            <li>Option a</li>
            <li>Option b</li>
            <li>Option c</li>
            <li>Option d</li>
        </ol>
    </div>
</body>

答案 3 :(得分:2)

请注意,此问题与一个月前提出的问题重复,我已经回答了 here 。我只会在那个链接上保留答案,因为这个问题应该在几年前就已经关闭了。

答案副本

我已经找到了meteor-autocomplete的textarea插入符号插件,所以我已经评估了GitHub上的所有8个插件。到目前为止,获胜者是来自组件textarea-caret-position

功能

  • 像素精度
  • 无任何依赖
  • 浏览器兼容性:Chrome,Safari,Firefox(尽管它有two bugs),IE9 +;可以工作,但未在Opera,IE8或更早版本中测试
  • 支持任何字体系列和大小,以及文本转换
  • 文本区域可以有任意填充或边框
  • 不会被textarea中的水平或垂直滚动​​条混淆
  • 支持硬回车,标签(IE上除外)和文本中的连续空格
  • 在比文本区域中的列长的行上更正位置
  • 在包装长字时,
  • 在行尾没有"ghost" position in the empty space

这是一个演示 - http://jsfiddle.net/dandv/aFPA7/

enter image description here

如何运作

镜像<div>在屏幕外创建,样式与<textarea>完全相同。然后,将插入到插入符号的textarea文本复制到div中,并在其后插入<span>。然后,跨度的文本内容被设置为textarea中文本的剩余部分,以便忠实地再现人造div中的包装。

这是保证处理与包装长行相关的所有边缘情况的唯一方法。它也被GitHub用来确定 @ 用户下拉列表的位置。

答案 4 :(得分:1)

blog似乎也很接近这个问题。我没有尝试过我自己,但作者说用FF3,Chrome,IE,Opera,Safari进行了测试。代码在GitHub

答案 5 :(得分:1)

在此修复:http://jsfiddle.net/eMwKd/4/

唯一的缺点是已经提供的函数getCaret()在按键时解析到错误的位置。因此,除非您释放密钥,否则红色光标似乎位于真实光标后面。

我将再看看它。

更新:嗯,如果行太长,自动换行就不准确了。

答案 6 :(得分:0)

This博客文章似乎解决了您的问题,但不幸的是,作者承认他只在IE 6中对其进行了测试。

  

IE中的DOM不提供有关字符相对位置的信息;但是,它确实为浏览器呈现的控件提供了边界和偏移值。因此,我使用这些值来确定字符的相对边界。然后,使用JavaScript TextRange,我创建了一种机制来处理这些度量,以计算给定TextArea中固定宽度字体的行和列位置。

     

首先,必须根据使用的固定宽度字体的大小计算TextArea的相对边界。为此,TextArea的原始值必须存储在本地JavaScript变量中并清除该值。然后,创建TextRange以确定TextArea的顶部和左边界。

答案 7 :(得分:0)

我不知道textarea的解决方案,但它确实适用于div contenteditable

您可以使用Range API。像这样:(是的,你真的只需要这3行代码)

// get active selection
var selection = window.getSelection();
// get the range (you might want to check selection.rangeCount
// to see if it's popuplated)
var range = selection.getRangeAt(0);

// will give you top, left, width, height
console.log(range.getBoundingClientRect());

我不确定浏览器兼容性,但我发现它适用于最新的Chrome,Firefox甚至IE7(我认为我测试了7,否则它是9)。

你甚至可以做'疯狂'这样的事情:如果你输入"#hash"并且光标位于最后h,你可以查看{{1}的当前范围}字符,将范围移回#个字符并获取该范围的 bounding-rect ,这将使popup-div看起来“坚持”到该单词。

一个小缺点是n有时可能有点儿麻烦。光标喜欢去不可能的地方,你现在必须处理HTML输入。但我确信浏览器厂商会解决这些问题,更多网站开始使用它们。

我可以给出的另一个提示是:查看rangy库。它试图成为一个功能齐全的交叉兼容范围库。你不需要它,但是如果你正在处理旧的浏览器,它可能值得你。

答案 8 :(得分:0)

也许这会让你高兴,它会告诉你选择的位置和光标的位置,所以试着通过点击获取选择按钮来检查计时器以获得自动位置或取消选中以获得位置

   <form>
 <p>
 <input type="button" onclick="evalOnce();" value="Get Selection">
timer:
<input id="eval_switch" type="checkbox" onclick="evalSwitchClicked(this)">
<input id="eval_time" type="text" value="200" size="6">
ms
</p>
<textarea id="code" cols="50" rows="20">01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 Sample text area. Please select above text. </textarea>
<textarea id="out" cols="50" rows="20"></textarea>
</form>
<div id="test"></div>
<script>

function Selection(textareaElement) {
this.element = textareaElement;
}
Selection.prototype.create = function() {
if (document.selection != null && this.element.selectionStart == null) {
return this._ieGetSelection();
} else {
return this._mozillaGetSelection();
}
}
Selection.prototype._mozillaGetSelection = function() {
return {
start: this.element.selectionStart,
end: this.element.selectionEnd
 };
 }
Selection.prototype._ieGetSelection = function() {
this.element.focus();
var range = document.selection.createRange();
var bookmark = range.getBookmark();
var contents = this.element.value;
var originalContents = contents;
var marker = this._createSelectionMarker();
while(contents.indexOf(marker) != -1) {
marker = this._createSelectionMarker();
 }
var parent = range.parentElement();
if (parent == null || parent.type != "textarea") {
return { start: 0, end: 0 };
}
range.text = marker + range.text + marker;
contents = this.element.value;
var result = {};
result.start = contents.indexOf(marker);
contents = contents.replace(marker, "");
result.end = contents.indexOf(marker);
this.element.value = originalContents;
range.moveToBookmark(bookmark);
range.select();
return result;
}
Selection.prototype._createSelectionMarker = function() {
return "##SELECTION_MARKER_" + Math.random() + "##";
}

var timer;
var buffer = "";
function evalSwitchClicked(e) {
if (e.checked) {
evalStart();
} else {
evalStop();
}
}
function evalStart() {
var o = document.getElementById("eval_time");
timer = setTimeout(timerHandler, o.value);
}
function evalStop() {
clearTimeout(timer);
}
function timerHandler() {
clearTimeout(timer);
var sw = document.getElementById("eval_switch");
if (sw.checked) {
evalOnce();
evalStart();
}
}
function evalOnce() {
try {
var selection = new Selection(document.getElementById("code"));
var s = selection.create();
var result = s.start + ":" + s.end;
buffer += result;
flush();
 } catch (ex) {
buffer = ex;
flush();
}
}
function getCode() {
// var s.create()
// return document.getElementById("code").value;
}
function clear() {
var out = document.getElementById("out");
out.value = "";
}
function print(str) {
buffer += str + "\n";
}
function flush() {
var out = document.getElementById("out");
out.value = buffer;
buffer = "";
 } 
</script>

在这里查看演示:jsbin.com

答案 9 :(得分:0)

有一个针对插入符号偏移的黑客描述: Textarea X/Y caret coordinates - jQuery plugin

如果你可以使用html5功能,最好使用带有contenteditable属性的div元素。

答案 10 :(得分:-1)

如何将span元素附加到克隆div并根据此span的偏移设置假光标?我更新了你的小提琴here。这里只有JS位

// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
var map = [];
var pan = '<span>|</span>'

//found @ http://davidwalsh.name/detect-scrollbar-width

function getScrollbarWidth() {
    var scrollDiv = document.createElement("div");
    scrollDiv.className = "scrollbar-measure";
    document.body.appendChild(scrollDiv);

    // Get the scrollbar width
    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;

    // Delete the DIV 
    document.body.removeChild(scrollDiv);

    return scrollbarWidth;
}

function getCaret(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() {
    var span = $('#pos span');
    var textarea = $('textarea');

    var note = $('#note');

    css = getComputedStyle(document.getElementById('textarea'));
    try {
        for (i in css) note.css(css[i]) && (css[i] != 'width' && css[i] != 'height') && note.css(css[i], css.getPropertyValue(css[i]));
    } catch (e) {}

    note.css('max-width', '300px');
    document.getElementById('note').style.visibility = 'hidden';
    var height = note.height();
    var fakeCursor, hidePrompt;

    textarea.on('keyup click', function(e) {
        if (document.getElementById('textarea').scrollHeight > 100) {
            note.css('max-width', 300 - getScrollbarWidth());
        }

        var pos = getCaret(textarea[0]);

        note.text(textarea.val().substring(0, pos));
        $(pan).appendTo(note);
        span.text(pos);

        if (hidePrompt) {
            hidePrompt.remove();
        }
        if (fakeCursor) {
            fakeCursor.remove();
        }

        fakeCursor = $("<div style='width:5px;height:30px;background-color: #777;position: absolute;z-index:10000'>&nbsp;</div>");

        fakeCursor.css('opacity', 0.5);
        fakeCursor.css('left', $('#note span').offset().left + 'px');
        fakeCursor.css('top', textarea.offset().top + note.height() - (30 + textarea.scrollTop()) + 'px');

        hidePrompt = fakeCursor.clone();
        hidePrompt.css({
            'width': '2px',
            'background-color': 'white',
            'z-index': '1000',
            'opacity': '1'
        });

        hidePrompt.appendTo(textarea.parent());
        fakeCursor.appendTo(textarea.parent());



        return true;
    });
});

更新:如果第一行不包含强硬换行符,我可以看到有错误,但是如果它没有,它似乎运行良好。