Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get character position when click on text in javascript

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:

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.

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):

    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.

like image 490
jcubic Avatar asked Jul 26 '17 16:07

jcubic


People also ask

How to find position of character in string JavaScript?

JavaScript String indexOf() The indexOf() method returns the position of the first occurrence of a value in a string. The indexOf() method returns -1 if the value is not found. The indexOf() method is case sensitive.

How do I know my input cursor position?

If you ever had a specific case where you had to retrieve the position of a caret (your cursor's position) inside an HTML input field, you can do so using the selectionStart property of an input's value.


1 Answers

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>
like image 55
Will Avatar answered Oct 17 '22 21:10

Will