如何在javascript中单击文本时获取字符位置

时间:2021-04-08 08:58:54

I have this function to get position of the cursor when you click on the text, it only works for monospace characters which is fine, but it obviously don't work with characters that are wider like Chinese or Japanese ones.

我有这个功能来点击文本时获取光标的位置,它只适用于等宽字符,这很好,但它显然不适用于像中文或日文更宽的字符。

    function get_char_pos(point) {
        var prompt_len = self.find('.prompt').text().length;
        var size = get_char_size();
        var width = size.width;
        var height = size.height;
        var offset = self.offset();
        var col = Math.floor((point.x - offset.left) / width);
        var row = Math.floor((point.y - offset.top) / height);
        var lines = get_splited_command_line(command);
        var try_pos;
        if (row > 0 && lines.length > 1) {
            try_pos = col + lines.slice(0, row).reduce(function(sum, line) {
                return sum + line.length;
            }, 0);
        } else {
            try_pos = col - prompt_len;
        }
        // tabs are 4 spaces and newline don't show up in results
        var text = command.replace(/\t/g, '\x00\x00\x00\x00').replace(/\n/, '');
        var before = text.slice(0, try_pos);
        var len = before.replace(/\x00{4}/g, '\t').replace(/\x00+/, '').length;
        return len > command.length ? command.length : len;
    }

I've tried to create a function using wcwidth library (that return 2 for wider characters and 1 for normal letters) but it don't work quite right, here is the code with demo:

我试图使用wcwidth库创建一个函数(对于更宽的字符返回2,对于普通字母返回1)但它不能正常工作,这是带有demo的代码:

var self = $('pre');
var offset = self.offset();
var command = 'チトシタテイトチトシイスチトシタテイトチトシイスチトシタテイトチトシイス\nfoo bar baz\nfoo bar baz\nチトシタテイトチトシイ';
self.html(command);
function get_char_size() {
    var span = $('<span>&nbsp;</span>').appendTo(self);
    var rect = span[0].getBoundingClientRect();
    span.remove();
    return rect;
}
var length = wcwidth;
// mock
function get_splited_command_line(string) {
    return string.split('\n');
}
function get_char_pos(point) {
    var size = get_char_size();
    var width = size.width;
    var height = size.height;
    var offset = self.offset();
    var col_count = Math.floor((point.x - offset.left) / width);
    var row = Math.floor((point.y - offset.top) / height);
    var lines = get_splited_command_line(command);
    var line = lines[row];
    var col = 0;
    var i = col_count;
    while (i > 0) {
        i -= length(line[col]);
        col++;
    }
    var try_pos;
    if (row > 0 && lines.length > 1) {
        try_pos = col + lines.slice(0, row).reduce(function(sum, line) {
            return sum + length(line);
        }, 0);
    } else {
        try_pos = col;
    }
    // tabs are 4 spaces and newline don't show up in results
    var text = command.replace(/\t/g, '\x00\x00\x00\x00').replace(/\n/, '');
    var before = text.slice(0, try_pos);
    var len = before.replace(/\x00{4}/g, '\t').replace(/\x00+/, '').length;
    var command_len = command.length;
    return len > command_len ? command_len : len;
}
self.click(function(e) {
  var pos = get_char_pos({
      x: e.pageX,
      y: e.pageY
  });
  self.html(command.substring(0, pos-1) + '<span>' +
            command[pos] + '</span>' +
            command.substring(pos+1));
});
span {
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/jcubic/leash/master/lib/wcwidth.js"></script>
<pre></pre>

I can't use spans for each letter because I have the text split into 3 spans before, cursor and after the cursor. and I also have spans for styling that may be or not in the container.

我不能为每个字母使用跨度,因为我将文本拆分为3个跨度,光标和光标之后。而且我也有可能在容器中或不在容器中的样式的跨度。

Any help how to fix this function is appreciated.

任何帮助如何修复此功能表示赞赏。

SOLUTION:

Here is my code based on @Will that I've used in my code that work with multiple elements (for some reason chrome have issues when you click on element that have only one character, and just in case the focus is not more then the length of the text):

这是我基于@Will的代码,我在我的代码中使用了多个元素(由于某些原因,当你点击只有一个字符的元素时,chrome会出现问题,以防焦点不是更多文字长度):

    function get_focus_offset() {
        var sel;
        if ((sel = window.getSelection()) && (sel.focusNode !== null)) {
            return sel.focusOffset;
        }
    }
    function get_char_pos(e) {
        var focus = get_focus_offset();
        if ($.isNumeric(focus)) {
            var node = $(e.target);
            // [role="presentation"] is my direct children that have
            // siblings that are other nodes with text
            var parent = node.closest('[role="presentation"]');
            var len = node.text().length;
            focus = len === 1 ? 0 : Math.min(focus, len);
            return focus + parent.prevUntil('.prompt').text_length() +
                node.prevAll().text_length();
        } else {
            return command.length;
        }
    }

UPDATE: there is issue with clicking if you click on the first half the character it get selected correctly but when you click other half it select next character so I ended up with one character per element approach.

更新:如果您单击正确选择的前半部分字符,则单击会出现问题,但是当您单击另一半时,它会选择下一个字符,因此我最终每个元素的方法都有一个字符。

2 个解决方案

#1


1  

Third attempt. Stuff a pipe character in there to pretend to be a cursor.

第三次尝试。在那里填充一个管道字符,假装是一个光标。

https://developer.mozilla.org/en-US/docs/Web/API/Selection

window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.charPosition').forEach(el => {
    let clean, cursor;
    el.addEventListener('click', e => {
        let position = window.getSelection().focusOffset;
        if (cursor && position > cursor)
            position--;
        if (clean)
            el['innerText'] = clean;
        let textnode = el.firstChild['splitText'](position);
        clean = textnode.wholeText;
        cursor = position;
        el.insertBefore(document.createTextNode('|'), textnode);
        el['innerText'] = textnode.wholeText;
    });
});
});
<div class="charPosition">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>

#2


5  

window.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('.charPosition').forEach(el => {
        let characters = el['innerText'].split('');
        el.innerHTML = '';
        characters.forEach(char => {
            let span = document.createElement('span');
            span.innerText = char;
            span.addEventListener('click', function () {
                let position = 0;
                let el = this;
                while (el.previousSibling !== null) {
                    position++;
                    el = el.previousSibling;
                }
                console.log(this.innerHTML + ':' + position);
            });
            el.appendChild(span);
        });
    });
});
<div class="charPosition">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>

#1


1  

Third attempt. Stuff a pipe character in there to pretend to be a cursor.

第三次尝试。在那里填充一个管道字符,假装是一个光标。

https://developer.mozilla.org/en-US/docs/Web/API/Selection

window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.charPosition').forEach(el => {
    let clean, cursor;
    el.addEventListener('click', e => {
        let position = window.getSelection().focusOffset;
        if (cursor && position > cursor)
            position--;
        if (clean)
            el['innerText'] = clean;
        let textnode = el.firstChild['splitText'](position);
        clean = textnode.wholeText;
        cursor = position;
        el.insertBefore(document.createTextNode('|'), textnode);
        el['innerText'] = textnode.wholeText;
    });
});
});
<div class="charPosition">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>

#2


5  

window.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('.charPosition').forEach(el => {
        let characters = el['innerText'].split('');
        el.innerHTML = '';
        characters.forEach(char => {
            let span = document.createElement('span');
            span.innerText = char;
            span.addEventListener('click', function () {
                let position = 0;
                let el = this;
                while (el.previousSibling !== null) {
                    position++;
                    el = el.previousSibling;
                }
                console.log(this.innerHTML + ':' + position);
            });
            el.appendChild(span);
        });
    });
});
<div class="charPosition">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>