如何在当前光标位置插入文本到文本域?

199

我想创建一个简单的函数,将文本添加到用户光标位置的文本区域中。它需要是一个简洁的函数,只包含基本功能。我可以自己处理其余部分。


3
可能是如何在光标位置插入文本?的重复问题。 - Derek 朕會功夫
2
看一下已经发布的这个答案:https://dev59.com/GlLTa4cB1Zd3GeqPcrPh - John Culviner
1
可能是在使用Javascript / jQuery的光标处插入文本的重复问题。 - user
如果你正在寻找一个带有撤销支持的简单模块,请尝试 insert-text-textarea。如果你需要 IE8+ 支持,请试试 insert-text-at-cursor 包。 - fregante
重复的问题 https://dev59.com/vHNA5IYBdhLWcg3wKam3 - ATYB
14个回答

166

使用输入元素的selectionStart/selectionEnd 属性(对于<textarea>也适用)

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}

31
为了修复“丢失插入符号位置”的问题:在 } else { 之前添加以下代码行:myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;这将使得文本框中的插入符号回到正确的位置。请注意,此操作不会改变原始代码的含义,只是添加了一些代码以解决问题。 - user340140
14
感谢 Rab 的回答,以及 @user340140 的修正。这里是一个可用的例子:http://jsfiddle.net/Znarkus/Z99mK/。 - Markus Hedlund
2
@user340140,你的“失去插入符位置”的修复方法只有在我在你建议的行之前将焦点放在输入框上才有效。至少在Chrome(当前版本62.0)中似乎无法更改非聚焦字段的选择。 - Jette
2
这段代码存在一个小问题:selectionStart 是一个数值,因此应该与 0 进行比较,而不是 '0',并且可能应该使用 === - Herohtar

109

这段代码可以用几行jQuery 1.9+来帮助你实现它:http://jsfiddle.net/4MBUG/2/

$('input[type=button]').on('click', function() {
    var cursorPos = $('#text').prop('selectionStart');
    var v = $('#text').val();
    var textBefore = v.substring(0,  cursorPos);
    var textAfter  = v.substring(cursorPos, v.length);

    $('#text').val(textBefore + $(this).val() + textAfter);
});

太好了!只需进行一些小修改,就可以在1.6上运行。 - Șerban Ghiță
1
但它无法替换所选文本。 - Serhii Holinei
11
感谢提供一个可用的示例。我已将其更新为可以重置光标位置,并将其转化为了jQuery插件:http://jsfiddle.net/70gqn153/ - freedomn-m
这个可以运行,但光标停留在错误的位置。 - Johann
这是最好的工作解决方案,其他我见过的都太过复杂,但这个简单易懂,谢谢! - K. P.
显示剩余2条评论

65

新答案:

https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLInputElement/setRangeText

不过我不确定这个在浏览器中的支持情况。

已在 Chrome 81 中测试通过。

function typeInTextarea(newText, el = document.activeElement) {
  const [start, end] = [el.selectionStart, el.selectionEnd];
  el.setRangeText(newText, start, end, 'select');
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>
<div>It'll replace a selection with the given text.</div>

旧答案:

对Erik Pukinskis'答案的纯JS修改:

function typeInTextarea(newText, el = document.activeElement) {
  const start = el.selectionStart
  const end = el.selectionEnd
  const text = el.value
  const before = text.substring(0, start)
  const after  = text.substring(end, text.length)
  el.value = (before + newText + after)
  el.selectionStart = el.selectionEnd = start + newText.length
  el.focus()
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>

已在Chrome 47、81和Firefox 76中进行过测试。

如果您想在键入时更改当前选定文本的值(以实现自动完成或类似效果),请将document.activeElement作为第一个参数传递。

这并不是最优雅的方法,但相当简单。

示例用法:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));

你没有用 >> ; << 关闭这行代码。 - Phoenix
5
在JavaScript中,分号是可选的。即使没有分号,代码也能正常运行。不过,如果你想加上分号,也没问题。这不是大问题。 - Jayant Bhawal
3
我已经在JSFiddle上制作了一个演示。它也可以在Chrome Canary 54.0.2813.0上使用。最后,如果你想按ID将其插入到文本框中,请在函数中使用document.getElementById('insertyourIDhere')代替el - bb216b3acfd8f72cbc8f899d4d6963
2
嘿@ErikAigner!我的错,我没有意识到这个问题有两个Erik的答案。我指的是Erik Pukinskis。我会更新答案以更好地反映这一点。 - Jayant Bhawal
1
如果尝试execCommand失败,最好先尝试它,然后再退回到这个。setRangeText将清除撤销/重做堆栈。MDN认为execCommand已经过时,但是如果您关心用户体验,则唯一的替代方法是自己重新实现撤销/重做堆栈。 - Jools
显示剩余4条评论

49
为了正确的Javascript。
HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
  text = text || '';
  if (document.selection) {
    // IE
    this.focus();
    var sel = document.selection.createRange();
    sel.text = text;
  } else if (this.selectionStart || this.selectionStart === 0) {
    // Others
    var startPos = this.selectionStart;
    var endPos = this.selectionEnd;
    this.value = this.value.substring(0, startPos) +
      text +
      this.value.substring(endPos, this.value.length);
    this.selectionStart = startPos + text.length;
    this.selectionEnd = startPos + text.length;
  } else {
    this.value += text;
  }
};

最佳解决方案!谢谢。 - Dima Melnik
11
将对象的原型进行扩展并不是一个好主意,只需要将其作为一个普通函数即可,它同样可以正常运行。 - fregante
1
在设置 this.value = ... 后,这将清除编辑元素的撤消缓冲区。有没有办法保留它? - c00000fd
@fregante 那么为什么要有原型系统呢?如果你一开始只使用自己的类型,就没有必要扩展原型。扩展是原型系统的用途所在。 - user187676
2
@ErikAigner 这是不正确的。在 ES6 之前,A.prototype.fn = X 是拥有“类”/继承的唯一方法。仅仅因为你可以扩展你的对象,这并不意味着你应该扩展_原生_对象。想象一下十年前你实现了 Array.map(),然后 Array.map() 成为了一个原生 API,但与你的不兼容。现在有人打开你的代码库,并看到 [].map() 并假设它是原生 API。问题来了,错误来了。 - fregante
显示剩余2条评论

28

这是一个简单的解决方案,适用于Firefox、Chrome、Opera、Safari和Edge,但可能无法在旧的IE浏览器上运行。

var target = document.getElementById("mytextarea_id")

if (target.setRangeText) {
    //if setRangeText function is supported by current browser
    target.setRangeText(data)
} else {
    target.focus()
    document.execCommand('insertText', false /*no UI*/, data);
}

setRangeText函数允许您用提供的文本替换当前选择内容,或者如果没有选择,则将文本插入光标位置。据我所知,目前仅受火狐浏览器支持。

对于其他浏览器,有“insertText”命令,它仅影响当前聚焦的html元素,并具有与setRangeText相同的行为。

部分灵感来自于这篇文章


1
这几乎是正确的方法。你提供的文章作为一个完整的解决方案包含在这里:insert-text-at-cursor。然而,我更喜欢execCommand,因为它支持undo并且制作了insert-text-textarea。虽然不支持IE但更小。 - fregante
1
不幸的是,execCommand被MDN视为已过时:https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand 我不知道为什么,它似乎非常有用! - Richard
1
是的,execCommand用于其他浏览器,而对于Firefox,应使用setRangeText函数。 - Ramast
Ramast,这不是您的代码所做的。它将使用setRangeText而不是execCommand对于任何定义了它(大多数)的浏览器。您描述的行为,需要首先调用document.execCommand,然后检查返回值。如果它是false,则使用target.setRangeText。 - Jools
@Jools 如果支持 setRangeText,为什么不直接使用它而不是 execCommand?我为什么需要先尝试 execCommand? - Ramast
我想匹配你所描述的行为:“execCommand用于其他浏览器,而对于Firefox,则使用setRangeText函数。” 你可能希望使用execCommand,因为正如fregante所说,它支持“撤消”。如果你调用setRangeText,类似于设置.value,它会清除撤消/重做堆栈,例如Ctrl+Z和右键单击,撤消将变为不可用。 - Jools

12

我喜欢简单的 JavaScript,通常我会使用 jQuery。以下是我基于mparkuk的答案得出的:

function typeInTextarea(el, newText) {
    var start = el.prop("selectionStart")
    var end = el.prop("selectionEnd")
    var text = el.val()
    var before = text.substring(0, start)
    var after  = text.substring(end, text.length)
    el.val(before + newText + after)
    el[0].selectionStart = el[0].selectionEnd = start + newText.length
    el.focus()
}

$("button").on("click", function() {
    typeInTextarea($("textarea"), "some text")
    return false
})

这里是演示:http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101


11

Rab的答案很好,但对Microsoft Edge浏览器不起作用,因此我添加了一个适应Edge的小改动:

https://jsfiddle.net/et9borp4/

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}

9

function insertAtCaret(text) {
  const textarea = document.querySelector('textarea')
  textarea.setRangeText(
    text,
    textarea.selectionStart,
    textarea.selectionEnd,
    'end'
  )
}

setInterval(() => insertAtCaret('Hello'), 3000)
<textarea cols="60">Stack Overflow Stack Exchange Starbucks Coffee</textarea>


7
如果用户在插入文本后没有触摸输入,则永远不会触发“input”事件,且value属性将不反映更改。因此,在以编程方式插入文本后触发输入事件非常重要。仅聚焦字段是不够的。
以下是 Snorvarg's answer的副本,末尾带有一个输入触发器:
function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
    triggerEvent(myField,'input');
}

function triggerEvent(el, type){
  if ('createEvent' in document) {
    // modern browsers, IE9+
    var e = document.createEvent('HTMLEvents');
    e.initEvent(type, false, true);
    el.dispatchEvent(e);
  } else {
    // IE 8
    var e = document.createEventObject();
    e.eventType = type;
    el.fireEvent('on'+e.eventType, e);
  }
}

感谢plainjs.com提供的triggerEvent函数

更多关于oninput事件的信息请参考w3schools.com

我在为一个聊天应用程序创建一个表情选择器时发现了这个问题。如果用户只选择了一些表情符号然后点击“发送”按钮,输入框将从未被用户触摸过。当检查value属性时,它始终为空,即使插入的表情符号unicode在输入框中可见。原来,如果用户不触摸输入框,则“input”事件永远不会触发,解决方法是像这样触发它。花了很长时间才弄清楚这个问题... 希望能节省其他人的时间。


这是一个非常有用的提示,谢谢分享。 - Jacques Gaudin

2
下面的代码是Dmitriy Kubyshkin的https://github.com/grassator/insert-text-at-cursor包的TypeScript适配版本。

/**
 * Inserts the given text at the cursor. If the element contains a selection, the selection
 * will be replaced by the text.
 */
export function insertText(input: HTMLTextAreaElement | HTMLInputElement, text: string) {
  // Most of the used APIs only work with the field selected
  input.focus();

  // IE 8-10
  if ((document as any).selection) {
    const ieRange = (document as any).selection.createRange();
    ieRange.text = text;

    // Move cursor after the inserted text
    ieRange.collapse(false /* to the end */);
    ieRange.select();

    return;
  }

  // Webkit + Edge
  const isSuccess = document.execCommand("insertText", false, text);
  if (!isSuccess) {
    const start = input.selectionStart;
    const end = input.selectionEnd;
    // Firefox (non-standard method)
    if (typeof (input as any).setRangeText === "function") {
      (input as any).setRangeText(text);
    } else {
      if (canManipulateViaTextNodes(input)) {
        const textNode = document.createTextNode(text);
        let node = input.firstChild;

        // If textarea is empty, just insert the text
        if (!node) {
          input.appendChild(textNode);
        } else {
          // Otherwise we need to find a nodes for start and end
          let offset = 0;
          let startNode = null;
          let endNode = null;

          // To make a change we just need a Range, not a Selection
          const range = document.createRange();

          while (node && (startNode === null || endNode === null)) {
            const nodeLength = node.nodeValue.length;

            // if start of the selection falls into current node
            if (start >= offset && start <= offset + nodeLength) {
              range.setStart((startNode = node), start - offset);
            }

            // if end of the selection falls into current node
            if (end >= offset && end <= offset + nodeLength) {
              range.setEnd((endNode = node), end - offset);
            }

            offset += nodeLength;
            node = node.nextSibling;
          }

          // If there is some text selected, remove it as we should replace it
          if (start !== end) {
            range.deleteContents();
          }

          // Finally insert a new node. The browser will automatically
          // split start and end nodes into two if necessary
          range.insertNode(textNode);
        }
      } else {
        // For the text input the only way is to replace the whole value :(
        const value = input.value;
        input.value = value.slice(0, start) + text + value.slice(end);
      }
    }

    // Correct the cursor position to be at the end of the insertion
    input.setSelectionRange(start + text.length, start + text.length);

    // Notify any possible listeners of the change
    const e = document.createEvent("UIEvent");
    e.initEvent("input", true, false);
    input.dispatchEvent(e);
  }
}

function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement) {
  if (input.nodeName !== "TEXTAREA") {
    return false;
  }
  let browserSupportsTextareaTextNodes;
  if (typeof browserSupportsTextareaTextNodes === "undefined") {
    const textarea = document.createElement("textarea");
    textarea.value = "1";
    browserSupportsTextareaTextNodes = !!textarea.firstChild;
  }
  return browserSupportsTextareaTextNodes;
}


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