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> </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.
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.
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With