จะแทรกข้อความลงใน textarea ที่ตำแหน่งเคอร์เซอร์ปัจจุบันได้อย่างไร?


126

ฉันต้องการสร้างฟังก์ชันง่ายๆที่เพิ่มข้อความลงในพื้นที่ข้อความที่ตำแหน่งเคอร์เซอร์ของผู้ใช้ จำเป็นต้องมีฟังก์ชันที่สะอาด เพียงแค่พื้นฐาน ฉันหาส่วนที่เหลือได้



2
ดูคำตอบที่โพสต์ไว้แล้ว: stackoverflow.com/questions/4456545/…
John Culviner


บทความที่น่าสนใจในปี 2018: วิธีแทรกข้อความลงใน Textarea ที่ Cursor Fast
Delgan

หากคุณกำลังมองหาโมดูลที่เรียบง่ายด้วยการยกเลิกการสนับสนุนลองแทรกข้อความ textarea หากคุณต้องการการสนับสนุน IE8 + ให้ลองใช้แพ็คเกจinsert-text-at-cursor
อิสระ

คำตอบ:


117
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;
    }
}

19
เพื่อแก้ไข "สูญเสียตำแหน่ง} else { myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;
คาเร็ต

10
ขอบคุณ Rab สำหรับคำตอบและ @ user340140 สำหรับการแก้ไข นี่เป็นตัวอย่างการทำงาน
Znarkus

@ user340140 การแก้ไข "สูญเสียยาคาเร็ต" ของคุณจะใช้งานได้ก็ต่อเมื่อฉันให้ความสำคัญกับอินพุตก่อนบรรทัดที่คุณแนะนำ ดูเหมือนว่าจะเป็นไปไม่ได้ที่จะเปลี่ยนการเลือกในฟิลด์ที่ไม่โฟกัสอย่างน้อยใน Chrome (เวอร์ชันปัจจุบัน 62.0)
Jette

มีปัญหาเล็กน้อยเกี่ยวกับรหัสนี้: selectionStartเป็นค่าตัวเลขดังนั้นจึงควรเปรียบเทียบกับ0ไม่ใช่'0'และควรใช้===
Herohtar

82

ตัวอย่างข้อมูลนี้สามารถช่วยคุณได้ใน 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
แต่ไม่สามารถแทนที่ข้อความที่เลือกได้
Sergey Goliney

@mparkuk: ยังคงได้รับผลกระทบจากปัญหา "สูญเสียตำแหน่งคาเร็ต" ที่กล่าวถึงข้างต้นโดยผู้ใช้ 340140 (ขออภัยฉันควรจะแก้ไข แต่หมดเวลาแล้ว)
jbobbins

4
ขอบคุณที่จัดหาซอที่ใช้งานได้ ฉันได้อัปเดตเพื่อรีเซ็ตตำแหน่งคาเร็ต
freedomn-m

ใช้งานได้ แต่เคอร์เซอร์ไปสิ้นสุดที่ตำแหน่งที่ไม่ถูกต้อง
AndroidDev

36

เพื่อประโยชน์ในการใช้งาน 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;
  }
};

นามสกุลดีมาก! ทำงานได้ตามที่คาดไว้ ขอบคุณ!
Martin Johansson

ทางออกที่ดีที่สุด! ขอบคุณ
Dima Melnik

4
ไม่ใช่ความคิดที่ดีที่จะขยายต้นแบบของวัตถุที่คุณไม่ได้เป็นเจ้าของ เพียงแค่ทำให้เป็นฟังก์ชันปกติและใช้งานได้ดี
อิสระ

นี้ล้างยกเลิก buffer this.value = ...สำหรับองค์ประกอบแก้ไขหลังจากการตั้งค่า มีวิธีถนอมไหม
c00000fd

19

คำตอบใหม่:

https://developer.mozilla.org/en-US/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>

คำตอบเก่า:

การปรับเปลี่ยน JS ที่บริสุทธิ์ของคำตอบของ Erik Pukinskis:

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'));

คุณไม่ได้ปิดบรรทัดด้วย >>; <<
ฟีนิกซ์

4
อัฒภาค @Phoenix เป็นทางเลือกใน Javascript ใช้งานได้โดยไม่มีพวกเขาด้วย แม้ว่าคุณสามารถแก้ไขเป็นอัฒภาคได้หากต้องการ ไม่มี biggie.
Jayant Bhawal

3
ฉันทำการสาธิตเกี่ยวกับ JSFiddle มันยังใช้งานได้Version 54.0.2813.0 canary (64-bit)ซึ่งโดยพื้นฐานแล้ว Chrome Canary 54.0.2813.0 สุดท้ายถ้าคุณต้องการให้มันแทรกลงในกล่องข้อความด้วย ID ให้ใช้document.getElementById('insertyourIDhere')แทนelในฟังก์ชัน
haykam

ส่วนใดของคำตอบของฉันที่ไม่ใช่ JS "บริสุทธิ์" ฉันลืม C ++ ไปบ้างไหม
Erik Aigner

2
เฮ้ @ErikAigner! ฉันไม่ดีไม่ทราบว่าคำถามนี้มีคำตอบจาก Erik สองคน Erik Pukinskisฉันหมายถึง ฉันจะอัปเดตคำตอบเพื่อสะท้อนให้ดีขึ้น
Jayant Bhawal

15

วิธีง่ายๆที่ใช้ได้กับ 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ฟังก์ชันช่วยให้คุณสามารถแทนที่การเลือกปัจจุบันด้วยข้อความที่ให้มาหรือหากไม่มีการเลือกให้แทรกข้อความที่ตำแหน่งเคอร์เซอร์ มันรองรับเฉพาะ firefox เท่าที่ฉันรู้

สำหรับเบราว์เซอร์อื่นจะมีคำสั่ง "insertText" ซึ่งมีผลเฉพาะองค์ประกอบ html ที่โฟกัสอยู่ในขณะนี้และมีลักษณะการทำงานเช่นเดียวกับ setRangeText

ได้รับแรงบันดาลใจบางส่วนจากบทความนี้


นี่เกือบจะเป็นวิธีที่ถูกต้อง บทความที่คุณเชื่อมโยงนำเสนอโซลูชั่นเต็มรูปแบบเป็นแพคเกจ: แทรกข้อความที่เคอร์เซอร์ แต่ฉันชอบexecCommandเพราะสนับสนุนundoและทำให้แทรกข้อความ textarea ไม่รองรับ IE แต่มีขนาดเล็กกว่า
อิสระ

1
น่าเสียดายที่execCommandMDN ถือว่าล้าสมัยไปแล้ว: developer.mozilla.org/en-US/docs/Web/API/Document/execCommandฉันไม่รู้ว่าทำไมดูเหมือนว่าจะมีประโยชน์จริงๆ!
Richard

1
ใช่ execCommand ใช้สำหรับเบราว์เซอร์อื่นสำหรับ firefox ฟังก์ชัน setRangeText จะถูกใช้แทน
Ramast

Ramast นั่นไม่ใช่สิ่งที่รหัสของคุณทำ จะใช้ setRangeText แทน execCommand สำหรับเบราว์เซอร์ใด ๆ ที่กำหนด (ส่วนใหญ่) สำหรับพฤติกรรมที่คุณอธิบายคุณต้องเรียก document.execCommand ก่อนจากนั้นตรวจสอบค่าส่งคืน หากเป็นเท็จให้ใช้ target.setRangeText
Jools

@Jools ถ้ารองรับ setRangeText แล้วทำไมไม่ใช้แทน execCommand ล่ะ ทำไมฉันต้องลองใช้ execCommand ก่อน
Ramast

10

คำตอบของ 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

ฉันชอบ javascript ธรรมดาและฉันมักจะมี jQuery อยู่รอบ ๆ นี่คือสิ่งที่ฉันคิดขึ้นจากmparkuk's :

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


6

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>

4

หากผู้ใช้ไม่แตะอินพุตหลังจากใส่ข้อความแล้วเหตุการณ์ "อินพุต" จะไม่ถูกทริกเกอร์และแอตทริบิวต์ค่าจะไม่แสดงการเปลี่ยนแปลง ดังนั้นจึงเป็นสิ่งสำคัญที่จะทริกเกอร์เหตุการณ์อินพุตหลังจากแทรกข้อความโดยทางโปรแกรม โฟกัสสนามอย่างเดียวไม่พอ

ต่อไปนี้เป็นสำเนาคำตอบของ Snorvargพร้อมด้วยทริกเกอร์อินพุตที่ตอนท้าย:

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

ฉันค้นพบสิ่งนี้ขณะสร้างตัวเลือกอีโมจิสำหรับแชท หากผู้ใช้เลือกอิโมจิเพียงไม่กี่ตัวแล้วกดปุ่ม "ส่ง" ช่องป้อนข้อมูลจะไม่ถูกสัมผัสโดยผู้ใช้ เมื่อตรวจสอบแอตทริบิวต์ค่าจะว่างเปล่าเสมอแม้ว่ายูนิโคดอีโมจิที่แทรกจะมองเห็นได้ในช่องป้อนข้อมูล ปรากฎว่าหากผู้ใช้ไม่แตะฟิลด์เหตุการณ์ 'อินพุต' จะไม่เริ่มทำงานและวิธีแก้ปัญหาก็คือการทริกเกอร์เช่นนี้ ใช้เวลาพอสมควรในการคิดออก ... หวังว่ามันจะช่วยใครสักคนได้บ้าง


0

การโพสต์ฟังก์ชันที่แก้ไขสำหรับการอ้างอิงของตัวเอง ตัวอย่างนี้แทรกรายการที่เลือกจาก<select>ออบเจ็กต์และวางเครื่องหมายคาเร็ตไว้ระหว่างแท็ก:

//Inserts a choicebox selected element into target by id
function insertTag(choicebox,id) {
    var ta=document.getElementById(id)
    ta.focus()
    var ss=ta.selectionStart
    var se=ta.selectionEnd
    ta.value=ta.value.substring(0,ss)+'<'+choicebox.value+'>'+'</'+choicebox.value+'>'+ta.value.substring(se,ta.value.length)
    ta.setSelectionRange(ss+choicebox.value.length+2,ss+choicebox.value.length+2)
}

0
 /**
 * Usage "foo baz".insertInside(4, 0, "bar ") ==> "foo bar baz"
 */
String.prototype.insertInside = function(start, delCount, newSubStr) {
    return this.slice(0, start) + newSubStr + this.slice(start + Math.abs(delCount));
};


 $('textarea').bind("keydown keypress", function (event) {
   var val = $(this).val();
   var indexOf = $(this).prop('selectionStart');
   if(event.which === 13) {
       val = val.insertInside(indexOf, 0,  "<br>\n");
       $(this).val(val);
       $(this).focus();
    }
})

แม้ว่าสิ่งนี้อาจตอบคำถามได้ แต่จะเป็นการดีกว่าที่จะอธิบายส่วนสำคัญของคำตอบและอาจเป็นปัญหาเกี่ยวกับรหัส OPs
pirho

0

โค้ดด้านล่างนี้เป็นการดัดแปลง TypeScript ของแพ็คเกจhttps://github.com/grassator/insert-text-at-cursorโดย Dmitriy Kubyshkin


/**
 * 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;
}

-1

เปลี่ยนเป็น getElementById (myField)

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

3
นั่นจะกระทบ DOM มากกว่าที่คุณต้องการ .. การจัดเก็บmyfieldในเครื่องจะดีกว่ามากสำหรับประสิทธิภาพ
TMan

2
ว้าวซ้ำซากเกินไปจริงๆdocument.getElementById(myField)! ทำครั้งเดียวที่ด้านบนและใช้ชื่อตัวแปร คุณตั้งใจจะค้นหาองค์ประกอบเดียวกันซ้ำซ้อนกี่ครั้ง
doug65536
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.