How to get character position when clicking on text in javascript

I have this function to get the cursor position, when you click on the text, it only works for monospaced characters, but it obviously does not work with characters that are wider than Chinese or Japanese.

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 tried to create a function using the wcwidth library (which returns 2 for wider characters and 1 for regular letters), but this does not work completely correctly, here is the code with a 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 cannot use intervals for each letter, because I have text divided into 3 spans before, cursor, and after cursor. and I also have gaps for styling, which may or may not be in the container.

Any help on how to fix this feature is appreciated.

Decision

Here is my @Will based code that I used in my code that works with multiple elements (for some reason, chrome has problems when you click on an element that has only one character, and just in case the focus is no more text length):

  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 a click problem if you click on the first half of the character that he selected correctly, but when you click on the other half, he selects the next character so that I end up with one character on the element approach.

+5
source share
2 answers

Third attempt. Draw a pipe symbol 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
source

 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> 
+3
source

Source: https://habr.com/ru/post/1270234/


All Articles