如何在文本框中将下拉菜单定位到光标位置?

9
我该如何将下拉菜单定位在文本框内的光标位置?我发现这个问题已经被问了很多次,但我无法找到正确的解决方案。
这是JSBIN
请用您的建议帮助我。
提前感谢。

我想不出在文本框中实现这个的任何方法,因为你不能在文本框中插入元素。可能的方法是使用一个带有contentEditable="true"属性的div,并以某种方式将其格式化成类似于文本框的样式。你可以通过selectionStart确定偏移量,然后用abcd<div>yourdiv</div>efgh替换abcd[cursor]efgh。由于这并不真正回答你的问题,我将把它作为一条评论留下来。 - Sumurai8
是的,我尝试过使用contenteditable div,但在光标位置插入另一个div时遇到了问题。我正在尝试在输入符号“@”时在光标位置插入一个div,但找不到方法...使用.append()无法在我在任何位置键入“@”时在光标位置插入div。http://jsbin.com/uyufiw/25/edit - selvagsz
$('#target').selectionStart 或类似的内容可用于查找光标的偏移量,您可以使用此偏移量创建两个子字符串。一个从开头到光标,另一个从光标到结尾。然后,您可以使用 firststring + yourdiv + laststring 替换 $('#target') 的内容。要插入内容,您可以将 <ul></ul> 替换为所选择的选项(这只是半语法,但在 li 元素上类似 this.parent.outerHTML = this.value)。对不起,我无法在此计算机上进行测试,所以除了给出神秘的提示外,无法做太多事情。 - Sumurai8
2个回答

4
我知道这不是对问题的确切答案(此解决方案不使用文本区域,而是使用可编辑内容的div),但我认为没有任何方法可以使用事件、文本区域上的属性或函数,或选择对象上的属性或函数来获取x-y坐标。
我已经制作了一个示例在JSBin上。请注意,我没有测试其他浏览器的兼容性,并且它不会将插入符号返回到您离开的位置。我无法弄清楚代码。我相信window.getSelection()在IE中不起作用,在IE8-中它完全不同。您可能还想确保菜单不会从屏幕边缘开始显示。

HTML

<div id="target" contentEditable="true">Type @ to see the dropdown.... </div>
<div class="dropdown">
  <ul id="dropdown" class="dropdown-menu hide" role="menu" aria-labelledby="dropdownMenu">
    <li><a>One</a> </li>
    <li><a>Two</a></li>
    <li><a>Three</a></li>
    <li><a>Four</a> </li>

  </ul>
</div>

The CSS

#target {
  height: 100px;
  border: 1px solid black;
  margin-top: 50px;
}

#dummy {
  display: inline-block;
}

.dropdown {
  position: absolute;
  top: 0;
  left: 0;
}

Javascript和JQuery

$("#target").keydown( function(e) {

  if(e.which === 50 && e.shiftKey === true ) {
    //Prevent this event from actually typing the @
    e.preventDefault();

    //console.log( window.getSelection() );
    var sel = window.getSelection();

    var offset = sel.baseOffset;
    var node = sel.focusNode.parentNode;

    //Get the text before and after the caret
    var firsttext = node.innerHTML.substr(0,sel.baseOffset);
    var nexttext = (sel.baseOffset != sel.focusNode.length ) ? node.innerHTML.substr( sel.baseOffset, sel.focusNode.length) : "";

    //Add in @ + dummy, because @ is not in there yet on keydown
    node.innerHTML = firsttext + '@<div id="dummy"></div>' + nexttext;

    //Transfer all relevant data to the dropdown menu

    $('.dropdown').css('left', $('#dummy')[0].offsetLeft).css('top', $('#dummy')[0].offsetTop).prop('x-custom-offset', offset + 1);

    //Delete the dummy to keep it clean
    //This will split the contents into two text nodes, which we don't want
    //$('#dummy').remove(); 
    node.innerHTML = firsttext + '@' + nexttext;

    //Put the caret back in place where we left off
    //...I can't seem to figure out how to correctly set the range correctly...

    $('#dropdown').removeClass('hide').addClass('show');    
  } else {
    $('#dropdown').removeClass('show').addClass('hide');
    $('.dropdown').removeProp('x-custom-offset');
  }
});

$('#dropdown').on( 'click', 'li a', function( e ) {
  e.preventDefault();

  $('#target').html( function( i, oldtext ) {
    var firsttext = oldtext.substr( 0, $('.dropdown').prop('x-custom-offset') );
    var nexttext = oldtext.substr( $('.dropdown').prop('x-custom-offset'), oldtext.length );

    console.log( e );

    var inserttext = e.target.innerText;

    //Cleanup
    $('#dropdown').removeClass('show').addClass('hide');

    return firsttext + inserttext + nexttext;
  } );
} );

说明

这个示例基于您可以在 contentEditable 中插入元素并检索它相对于屏幕顶部和左侧的偏移量。当按下 shift + key 50 时,事件处理程序将阻止键入 @ 并替换为 @ + 虚拟对象本身。然后,我们从该对象中检索偏移量,并将下拉菜单移动到该偏移量。此外,我们将字符偏移量保存为菜单的自定义属性 x-custom-offset,以便我们可以在该特定位置插入值。然后,我们需要删除虚拟 div,但是如果我们使用 $('#dummy').remove() 删除虚拟 div,那么虚拟 div 前面的文本节点和虚拟 div 后面的文本节点将不会合并。如果我们要在其他地方放置另一个 @ 或放错位置,则将删除最后一个文本节点。因此,我们只需再次替换可编辑 div 的内容即可。最后,必须将光标设置回其原始位置。我无法正确地解决这个问题。

第二个处理程序用于将文本插入文本框中。代码应该是自我解释的。我们之前设置的 x-custom-offset 属性用于在文本框的正确位置插入文本。$('#dropdown').on( 'click', 'li a', function( e ) { ... } ); 将点击事件附加到 ul 而不是 li,因此如果您动态创建 li,它仍将继续工作(但只有在单击链接部分时才会触发)。


1
你可以获取鼠标的位置,然后将下拉列表移动到该位置。你只需要确保弹出内容具有比你想要遮盖的元素更高的z-index,并且其位置设置为absolute。
这是我曾经写过的一个小测试样本。
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
function toggleClass(element, newStr)
{
    index=element.className.indexOf(newStr);
    if ( index == -1)
        element.className += ' '+newStr;
    else
    {
        if (index != 0)
            newStr = ' '+newStr;
        element.className = element.className.replace(newStr, '');
    }
}
function forEachNode(nodeList, func)
{
    var i, n = nodeList.length;
    for (i=0; i<n; i++)
    {
        func(nodeList[i], i, nodeList);
    }
}

window.addEventListener('load', mInit, false);

function mInit()
{
}

function onShowBtn(e)
{
    var element = byId('popup');
    element.className = element.className.replace(' hidden', '');
    var str = '';//'border-radius: 32px; border: solid 5px;';
    e = e||event;
    str += "left: " + e.pageX + "px; top:"+e.pageY+"px;"
    element.setAttribute('style',str);
}
function onHideBtn()
{
    var element = byId('popup');
    if (element.className.indexOf(' hidden') == -1)
        element.className += ' hidden';
}

</script>
<style>
#controls
{
    display: inline-block;
    padding: 16px;
    border-radius: 6px;
    border: solid 1px #555;
    background: #AAA;
}
#popup
{
    border: solid 1px #777;
    border-radius: 4px;
    padding: 12px;
    background: #DDD;
    display: inline-block;
    z-index: 2;
    position: absolute;
}
#popup.hidden
{
    visibility: hidden;
}
</style>
</head>
<body>
    <div id='controls'>
        <input type='button' value='show' onclick='onShowBtn()'>
        <input type='button' value='hide' onclick='onHideBtn()'>
    </div>
    <br>
    <div id='popup'>
        <p>This is some assorted
            text</p>
            <hr>
        <ul>
            <li>item a</li>
            <li>item 2</li>
            <li>item iii</li>
        </ul>
    </div>
</body>
</html>

1
据我理解,OP正在询问如何在光标位置显示某些内容(即在文本框中显示输入位置的那个东西),换句话说,如何获取光标的坐标,而不是当用户按下键时鼠标所在的位置,也不是如何在这些坐标上显示某些内容。e.pageX在keyDown事件中不存在。 - Sumurai8
确实。我刚刚重新阅读了它。现在看起来非常清晰。那是一个与我错误回答的问题非常不同的问题。谢谢。 :) - enhzflep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接