วิธีการตั้งค่าเครื่องหมายรูปหมวก (เคอร์เซอร์) ในองค์ประกอบ contenteditable (div)?


191

ฉันมี HTML ง่ายๆนี้เป็นตัวอย่าง:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

ฉันต้องการสิ่งที่ง่าย - เมื่อฉันคลิกปุ่มฉันต้องการวางคาเร็ต (เคอร์เซอร์) ไว้ในตำแหน่งเฉพาะใน div ที่แก้ไขได้ จากการค้นหาบนเว็บฉันได้แนบไฟล์ JS ให้กับการคลิกปุ่ม แต่ไม่ได้ผล (FF, Chrome):

var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

เป็นไปได้ไหมที่จะตั้งคาเร็ตด้วยตนเองเช่นนี้?

คำตอบ:


261

ในเบราว์เซอร์ส่วนใหญ่คุณต้องการRangeและSelectionวัตถุ คุณระบุขอบเขตการเลือกแต่ละรายการเป็นโหนดและออฟเซ็ตภายในโหนดนั้น ตัวอย่างเช่นในการตั้งค่าเครื่องหมายรูปหมวกให้เป็นอักขระที่ห้าของข้อความบรรทัดที่สองคุณต้องทำดังนี้:

var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.childNodes[2], 5);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);

IE <9 ทำงานแตกต่างอย่างสิ้นเชิง หากคุณต้องการสนับสนุนเบราว์เซอร์เหล่านี้คุณจะต้องใช้รหัสอื่น

ตัวอย่าง jsFiddle: http://jsfiddle.net/timdown/vXnCM/


2
โซลูชันของคุณทำงานได้อย่างสมบูรณ์แบบ ขอบคุณมาก. มีโอกาสที่จะสามารถทำงานใน "บริบทข้อความ" - ซึ่งหมายความว่าอันดับ # 5 จะเป็นตัวอักษรตัวที่ห้าบนหน้าจอไม่ใช่ตัวอักษรตัวที่ห้าในรหัสหรือไม่?
Frodik

3
@Frodik: คุณสามารถใช้setSelectionRange()ฟังก์ชั่นจากคำตอบที่ผมเขียนนี่: stackoverflow.com/questions/6240139/... ดังที่ฉันได้กล่าวไว้ในคำตอบมีหลายสิ่งที่มันจะไม่จัดการอย่างถูกต้อง / สม่ำเสมอ แต่อาจดีพอ
ทิมดาวน์

7
วิธีการเกี่ยวกับการตั้งค่ารูปหมวกภายในแท็กช่วงเช่นนี้: << div id = "แก้ไข" contenteditable = "true"> test1 test2 <br> <br> <span> </ span> </ div>
Med อัก Z

1
@ MalcolmOcean: Barf เนื่องจาก IE <9 ไม่มีdocument.createRange(หรือwindow.getSelectionแต่จะไม่ไปไกลขนาดนั้น)
Tim Down

1
@undroid: jsfiddle ทำงานได้ดีสำหรับฉันใน Firefox 38.0.5 บน Mac
Tim Down

62

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

ในการตั้งค่าตำแหน่งเคอร์เซอร์ฉันมีฟังก์ชั่นนี้ซึ่งวนรอบโหนดข้อความลูกทั้งหมดภายในโหนดที่ระบุและตั้งช่วงจากจุดเริ่มต้นของโหนดเริ่มต้นไปยังอักขระchars.count :

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

ฉันจะเรียกรูทีนด้วยฟังก์ชันนี้:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

range.collapse (false) ตั้งเคอร์เซอร์ไปที่จุดสิ้นสุดของช่วง ฉันได้ทำการทดสอบกับ Chrome, IE, Mozilla และ Opera เวอร์ชันล่าสุดแล้วและพวกเขาก็ทำงานได้ดี

PS หากใครสนใจฉันจะได้ตำแหน่งเคอร์เซอร์ปัจจุบันโดยใช้รหัสนี้:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

รหัสไม่ตรงข้ามของฟังก์ชั่นการตั้งค่า - มันจะได้รับ window.getSelection ปัจจุบัน () focusNode และ focusOffset และนับถอยหลังทุกตัวอักษรข้อความที่พบจนกว่าจะถึงโหนดผู้ปกครองที่มีรหัสของ containerId ฟังก์ชั่น isChildOf เพียงตรวจสอบก่อนที่จะเรียกใช้ว่าโหนดที่จัดเป็นจริงลูกของparentId ที่ให้มา

รหัสควรจะทำงานได้อย่างตรงไปตรงมาโดยไม่มีการเปลี่ยนแปลง แต่ฉันเพิ่งนำมันมาจากปลั๊กอิน jQuery ที่ฉันพัฒนาขึ้นเพื่อที่จะแฮ็คข้อมูลสองสามรายการนี้ - แจ้งให้เราทราบหากมีอะไรไม่ทำงาน!


1
คุณช่วยให้ jsfiddle ทำงานได้ไหม? ฉันกำลังดิ้นรนเพื่อหาวิธีการทำงานในขณะที่ฉันไม่แน่ใจว่าอะไรnode.idและparentIdเกี่ยวข้องกับโดยไม่มีตัวอย่าง ขอบคุณ :)
Bendihossan

4
@Bendihossan - ลองjsfiddle.net/nrx9yvw9/5 - ด้วยเหตุผลบางอย่าง div เนื้อหาที่แก้ไขได้ในตัวอย่างนี้กำลังเพิ่มตัวละครบางตัวและส่งคืนแคร่ที่จุดเริ่มต้นของข้อความ (อาจเป็น jsfiddle ; ทำเช่นเดียวกันบนเซิร์ฟเวอร์ asp.net ของฉัน)
เลียม

@Bendihossan - องค์ประกอบ HTML ภายใน div contenteditable ได้รับการแบ่งออกเป็นโครงสร้างต้นไม้ที่มีหนึ่งโหนดสำหรับแต่ละองค์ประกอบ HTML getCurrentCursorPosition ได้รับตำแหน่งการเลือกปัจจุบันและกลับไปที่ต้นไม้นับจำนวนตัวอักษรข้อความธรรมดาที่มี Node.id คือรหัสองค์ประกอบ html ในขณะที่ parentId อ้างถึงรหัสองค์ประกอบ HTML ก็ควรหยุดนับกลับไป
Liam

1
มีอยู่ในรายการสิ่งที่ต้องทำของฉันเพื่อเขียนอันที่แยกจากรหัส UI ของฉัน - ฉันจะโพสต์เมื่อฉันมีวินาที
เลียม

1
เพื่อให้สามารถทดสอบโซลูชันที่แตกต่างกันได้อย่างรวดเร็วคุณสามารถแก้ไขคำตอบของคุณให้เป็นข้อมูลโค้ดที่ทำงานได้หรือไม่? ขอบคุณล่วงหน้า.
Basj

3

หากคุณไม่ต้องการใช้ jQuery คุณสามารถลองวิธีนี้:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDivคุณแก้ไของค์ประกอบได้อย่าลืมตั้งค่าidไว้ จากนั้นคุณจะต้องได้รับinnerHTMLจากองค์ประกอบและตัดสายเบรกทั้งหมด และเพิ่งตั้งค่ายุบด้วยอาร์กิวเมนต์ถัดไป


3
  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }

3

function set_mouse() {
  var as = document.getElementById("editable");
  el = as.childNodes[1].childNodes[0]; //goal is to get ('we') id to write (object Text) because it work only in object text
  var range = document.createRange();
  var sel = window.getSelection();
  range.setStart(el, 1);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);

  document.getElementById("we").innerHTML = el; // see out put of we id
}
<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd
  <p>dd</p>psss
  <p>dd</p>
  <p>dd</p>
  <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>

มันยากมากที่ตั้งคาเร็ตไว้ในตำแหน่งที่เหมาะสมเมื่อคุณมีองค์ประกอบขั้นสูงเช่น (p) (ช่วง) ฯลฯ เป้าหมายคือการได้รับ (วัตถุข้อความ):

<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
    <p>dd</p>
    <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>

    function set_mouse() {
        var as = document.getElementById("editable");
        el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
        var range = document.createRange();
        var sel = window.getSelection();
        range.setStart(el, 1);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        document.getElementById("we").innerHTML = el;// see out put of we id
    }
</script>

1
เพื่อให้สามารถทดสอบคำตอบของคุณได้อย่างรวดเร็วคุณสามารถแก้ไขคำตอบของคุณให้เป็นข้อมูลโค้ดที่ทำงานได้หรือไม่? ขอบคุณล่วงหน้า.
Basj

1

ฉันกำลังเขียนปากกาเน้นข้อความ (และโปรแกรมแก้ไขโค้ดพื้นฐาน) และฉันจำเป็นต้องรู้วิธีพิมพ์อักขระคำพูดเดี่ยวอัตโนมัติและย้ายเครื่องหมายรูปหมวกกลับ (เช่นตัวแก้ไขโค้ดจำนวนมากในปัจจุบัน)

นี่เป็นตัวอย่างของโซลูชันของฉันด้วยความช่วยเหลือจากเธรดนี้เอกสาร MDN และการดูคอนโซล moz มากมาย ..

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

สิ่งนี้อยู่ในองค์ประกอบ div ที่พึงพอใจ

ฉันออกจากที่นี่เพื่อเป็นการขอบคุณโดยตระหนักว่ามีคำตอบที่ยอมรับแล้ว


1

ฉันทำสิ่งนี้เพื่อแก้ไขข้อความอย่างง่าย

ความแตกต่างจากวิธีอื่น:

  • ประสิทธิภาพสูง
  • ใช้ได้กับทุกพื้นที่

การใช้

// get current selection
const [start, end] = getSelectionOffset(container)

// change container html
container.innerHTML = newHtml

// restore selection
setSelectionOffset(container, start, end)

// use this instead innerText for get text with keep all spaces
const innerText = getInnerText(container)
const textBeforeCaret = innerText.substring(0, start)
const textAfterCaret = innerText.substring(start)

selection.ts

/** return true if node found */
function searchNode(
    container: Node,
    startNode: Node,
    predicate: (node: Node) => boolean,
    excludeSibling?: boolean,
): boolean {
    if (predicate(startNode as Text)) {
        return true
    }

    for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
        if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
            return true
        }
    }

    if (!excludeSibling) {
        let parentNode = startNode
        while (parentNode && parentNode !== container) {
            let nextSibling = parentNode.nextSibling
            while (nextSibling) {
                if (searchNode(container, nextSibling, predicate, true)) {
                    return true
                }
                nextSibling = nextSibling.nextSibling
            }
            parentNode = parentNode.parentNode
        }
    }

    return false
}

function createRange(container: Node, start: number, end: number): Range {
    let startNode
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            const dataLength = (node as Text).data.length
            if (start <= dataLength) {
                startNode = node
                return true
            }
            start -= dataLength
            end -= dataLength
            return false
        }
    })

    let endNode
    if (startNode) {
        searchNode(container, startNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                const dataLength = (node as Text).data.length
                if (end <= dataLength) {
                    endNode = node
                    return true
                }
                end -= dataLength
                return false
            }
        })
    }

    const range = document.createRange()
    if (startNode) {
        if (start < startNode.data.length) {
            range.setStart(startNode, start)
        } else {
            range.setStartAfter(startNode)
        }
    } else {
        if (start === 0) {
            range.setStart(container, 0)
        } else {
            range.setStartAfter(container)
        }
    }

    if (endNode) {
        if (end < endNode.data.length) {
            range.setEnd(endNode, end)
        } else {
            range.setEndAfter(endNode)
        }
    } else {
        if (end === 0) {
            range.setEnd(container, 0)
        } else {
            range.setEndAfter(container)
        }
    }

    return range
}

export function setSelectionOffset(node: Node, start: number, end: number) {
    const range = createRange(node, start, end)
    const selection = window.getSelection()
    selection.removeAllRanges()
    selection.addRange(range)
}

function hasChild(container: Node, node: Node): boolean {
    while (node) {
        if (node === container) {
            return true
        }
        node = node.parentNode
    }

    return false
}

function getAbsoluteOffset(container: Node, offset: number) {
    if (container.nodeType === Node.TEXT_NODE) {
        return offset
    }

    let absoluteOffset = 0
    for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) {
        const childNode = container.childNodes[i]
        searchNode(childNode, childNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                absoluteOffset += (node as Text).data.length
            }
            return false
        })
    }

    return absoluteOffset
}

export function getSelectionOffset(container: Node): [number, number] {
    let start = 0
    let end = 0

    const selection = window.getSelection()
    for (let i = 0, len = selection.rangeCount; i < len; i++) {
        const range = selection.getRangeAt(i)
        if (range.intersectsNode(container)) {
            const startNode = range.startContainer
            searchNode(container, container, node => {
                if (startNode === node) {
                    start += getAbsoluteOffset(node, range.startOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                start += dataLength
                end += dataLength

                return false
            })

            const endNode = range.endContainer
            searchNode(container, startNode, node => {
                if (endNode === node) {
                    end += getAbsoluteOffset(node, range.endOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                end += dataLength

                return false
            })

            break
        }
    }

    return [start, end]
}

export function getInnerText(container: Node) {
    const buffer = []
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            buffer.push((node as Text).data)
        }
        return false
    })
    return buffer.join('')
}

1

ฉันได้คำตอบของ @ เลียมอีกครั้ง ฉันใส่มันไว้ในคลาสด้วยวิธีการแบบสแตติกฉันทำให้ฟังก์ชั่นของมันรับองค์ประกอบแทน #id และการปรับแต่งขนาดเล็กอื่น ๆ

<div contenteditable="true">รหัสนี้จะเป็นสิ่งที่ดีสำหรับการแก้ไขเคอร์เซอร์ในกล่องข้อความที่อุดมไปด้วยที่คุณอาจจะทำด้วย ฉันติดอยู่กับเรื่องนี้หลายวันก่อนที่จะถึงรหัสด้านล่าง

แก้ไข: คำตอบของเขาและคำตอบนี้มีข้อผิดพลาดที่เกี่ยวข้องกับการกดปุ่ม Enter เนื่องจากการเข้าไม่นับเป็นตัวละครตำแหน่งเคอร์เซอร์จะเลอะหลังจากกด Enter หากฉันสามารถแก้ไขรหัสฉันจะอัปเดตคำตอบของฉัน

edit2: บันทึกตัวเองมากปวดหัวและให้แน่ใจว่าคุณมี<div contenteditable=true> display: inline-blockวิธีนี้จะแก้ไขข้อบกพร่องบางอย่างที่เกี่ยวข้องกับการวาง Chrome <div>แทน<br>เมื่อคุณกด Enter

วิธีใช้

let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// do stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();

รหัส

// Credit to Liam (Stack Overflow)
// https://stackoverflow.com/a/41034697/3480193
class Cursor {
    static getCurrentCursorPosition(parentElement) {
        var selection = window.getSelection(),
            charCount = -1,
            node;
        
        if (selection.focusNode) {
            if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                node = selection.focusNode; 
                charCount = selection.focusOffset;
                
                while (node) {
                    if (node === parentElement) {
                        break;
                    }

                    if (node.previousSibling) {
                        node = node.previousSibling;
                        charCount += node.textContent.length;
                    } else {
                        node = node.parentNode;
                        if (node === null) {
                            break;
                        }
                    }
                }
            }
        }
        
        return charCount;
    }
    
    static setCurrentCursorPosition(chars, element) {
        if (chars >= 0) {
            var selection = window.getSelection();
            
            let range = Cursor._createRange(element, { count: chars });

            if (range) {
                range.collapse(false);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    
    static _createRange(node, chars, range) {
        if (!range) {
            range = document.createRange()
            range.selectNode(node);
            range.setStart(node, 0);
        }

        if (chars.count === 0) {
            range.setEnd(node, chars.count);
        } else if (node && chars.count >0) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent.length < chars.count) {
                    chars.count -= node.textContent.length;
                } else {
                    range.setEnd(node, chars.count);
                    chars.count = 0;
                }
            } else {
                for (var lp = 0; lp < node.childNodes.length; lp++) {
                    range = Cursor._createRange(node.childNodes[lp], chars, range);

                    if (chars.count === 0) {
                    break;
                    }
                }
            }
        } 

        return range;
    }
    
    static _isChildOf(node, parentElement) {
        while (node !== null) {
            if (node === parentElement) {
                return true;
            }
            node = node.parentNode;
        }

        return false;
    }
}

0

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

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

ฉันยังเขียนโค้ดเพื่อรับตำแหน่งคาเร็ตปัจจุบัน (ไม่ได้ทดสอบ):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

คุณต้องระวัง range.startOffset และ range.endOffset มีการชดเชยอักขระสำหรับโหนดข้อความ (nodeType === 3) และโหนดย่อยสำหรับโหนดองค์ประกอบ (nodeType === 1) range.startContainer และ range.endContainer อาจอ้างถึงโหนดองค์ประกอบใด ๆ ของระดับใด ๆ ในต้นไม้ (แน่นอนพวกเขายังสามารถอ้างถึงโหนดข้อความ)


0

ขึ้นอยู่กับคำตอบของ Tim Down แต่จะตรวจสอบแถวข้อความ "ดี" ที่เป็นที่รู้จักล่าสุด มันวางเคอร์เซอร์ที่ปลายสุด

นอกจากนี้ฉันยังสามารถเรียกซ้ำ / ตรวจสอบซ้ำเด็กคนสุดท้ายของเด็กแต่ละคนติดต่อกันเพื่อหาโหนดข้อความ "ดี" สุดท้ายใน DOM

function onClickHandler() {
  setCaret(document.getElementById("editable"));
}

function setCaret(el) {
  let range = document.createRange(),
      sel = window.getSelection(),
      lastKnownIndex = -1;
  for (let i = 0; i < el.childNodes.length; i++) {
    if (isTextNodeAndContentNoEmpty(el.childNodes[i])) {
      lastKnownIndex = i;
    }
  }
  if (lastKnownIndex === -1) {
    throw new Error('Could not find valid text content');
  }
  let row = el.childNodes[lastKnownIndex],
      col = row.textContent.length;
  range.setStart(row, col);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);
  el.focus();
}

function isTextNodeAndContentNoEmpty(node) {
  return node.nodeType == Node.TEXT_NODE && node.textContent.trim().length > 0
}
<div id="editable" contenteditable="true">
  text text text<br>text text text<br>text text text<br>
</div>
<button id="button" onclick="onClickHandler()">focus</button>

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