最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

jquery - How to get character position when click on text in javascript - Stack Overflow

programmeradmin1浏览0评论

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=".1.1/jquery.min.js"></script>
<script src=".js"></script>
<pre></pre>

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.

Share Improve this question edited Oct 8, 2017 at 18:10 jcubic asked Jul 26, 2017 at 16:27 jcubicjcubic 66.6k58 gold badges249 silver badges450 bronze badges 7
  • 1 What is the use case for this? – charlietfl Commented Jul 26, 2017 at 16:35
  • 1 How many characters are you looking at? Would it be feasible to wrap each one in its own span? – J. Chen Commented Jul 26, 2017 at 17:09
  • @charlietfl I need this for jQuery Terminal to move the cursor on click. I already have this working but it don't work for wider characters. – jcubic Commented Jul 26, 2017 at 17:22
  • @J.Chen it will be hard because I need to have also spans with colors and style in container and will be hard to distinguish span that have style with span that is letter. – jcubic Commented Jul 26, 2017 at 17:24
  • By the way, do you need to get any information about character stylings ?(color, size, weight etc..) – Artem Arkhipov Commented Jul 26, 2017 at 18:57
 |  Show 2 more comments

2 Answers 2

Reset to default 11

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>

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>

发布评论

评论列表(0)

  1. 暂无评论