Caveat: it is recommended if you use this method to disable the ability to resize your textarea. This way you can control the adjustor needed to account for the difference of varying sizes of charactrer widths and how selection of characters varies in terms of actual width with textarea wordwrap.
NOTE: Did not test this on MOBILE VERSION.
I was able to get this to work using a couple of functions I found online here: How to get number of rows in textarea using JavaScript?. Basically the poster used the height, line-height, overflow and scroll-height to determine the height of the textarea and, in short, get the amount of lines within the textareas content. I included the posters original comments to help you understand the logic. I adjusted their second function to divide the amount of lines with the amount of characters within the textarea, this gives us a rudimentary idea of how wide each line is, although it is not precise! See Caveat... Depending on the characters within each line, which can change depending on the width of the textarea, the rounded amount of characters in the line will vary. So when we go up a line or down a line it will jump right or left depending on that variation.
How we get the line up/down...
let style = (window.getComputedStyle) ?
window.getComputedStyle(text) : text.currentStyle,
// This will get the line-height if it is set in the css
textLineHeight = parseInt(style.lineHeight, 10),
// Get the scroll height of the textarea
textHeight = calculateContentHeight(text, textLineHeight),
// calculate the number of lines by dividing
// the scroll height by the line-height
numberOfLines = Math.ceil(textHeight / textLineHeight),
// get the amount of characters in the textarea
numOfChars = text.value.length,
// this following number will vary depending on how the width of your
// lines character count is calculated and rounded in terms
// of what the actual width in character count actually is
// you will have to adjust this number accordingly
adjustor = 14,
// divide the number of characters by the amount of lines
percentage = numOfChars / numberOfLines + adjustor;
return percentage;
Again, this is not as precise with the up and down but it works, moving the cursor up or down on button press it shifts lightly left or right depending on the amount the rounded count of characters is against the amount on that particular line.
EDIT: I have combined your movement functions into one function that runs the button class through a forEach
loop and uses the event target to check the ID of each element and move the cursor accordingly.
let text = document.getElementById('text');
text.value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mauris in aliquam sem fringilla ut morbi tincidunt augue. Scelerisque in dictum non consectetur a erat nam. Consectetur adipiscing elit ut aliquam purus sit amet luctus venenatis. ";
// lets just set a cursor middle of the road for testing...
text.focus();
text.setSelectionRange(181, 181);
let calculateContentHeight = (text, scanAmount) => {
let origHeight = text.style.height,
height = text.offsetHeight,
scrollHeight = text.scrollHeight,
overflow = text.style.overflow;
/// only bother if the ta is bigger than content
if (height >= scrollHeight) {
/// check that our browser supports changing dimension
/// calculations mid-way through a function call...
text.style.height = `${(height + scanAmount)}px`;
/// because the scrollbar can cause calculation problems
text.style.overflow = 'hidden';
/// by checking that scrollHeight has updated
if (scrollHeight < text.scrollHeight) {
/// now try and scan the ta's height downwards
/// until scrollHeight becomes larger than height
while (text.offsetHeight >= text.scrollHeight) {
text.style.height = `${(height -= scanAmount)}px`;
}
/// be more specific to get the exact height
while (text.offsetHeight < text.scrollHeight) {
text.style.height = `${(height++)}px`;
}
/// reset the ta back to it's original height
text.style.height = origHeight;
/// put the overflow back
text.style.overflow = overflow;
return height;
}
} else {
return scrollHeight;
}
}
let calculateLineWidth = (text) => {
let style = (window.getComputedStyle) ?
window.getComputedStyle(text) : text.currentStyle,
// This will get the line-height only if it is set in the css,
// otherwise it's "normal"
textLineHeight = parseInt(style.lineHeight, 10),
// Get the scroll height of the textarea
textHeight = calculateContentHeight(text, textLineHeight),
// calculate the number of lines
numberOfLines = Math.ceil(textHeight / textLineHeight),
// get the amount of characters in the textarea
numOfChars = text.value.length,
// this number will vary depending on how the width of your
// lines character count is calculated and rounded in terms
// of what the actual width in character count actually is
// you will have to adjust this number accordingly
adjustor = 14,
// divide the number of characters by the amount of lines
percentage = numOfChars / numberOfLines + adjustor;
return percentage;
}
const moveCursor = (text) => {
const btns = document.querySelectorAll('.btns');
btns.forEach((btn) => {
btn.addEventListener('click', (e) => {
text.focus();
let pos = text.selectionStart;
if (e.target.id === 'left') {
pos--;
text.setSelectionRange(pos, pos);
} else if (e.target.id === 'right') {
pos++;
text.setSelectionRange(pos, pos);
} else if (e.target.id === 'up') {
if (pos - Number(calculateLineWidth(text)) > 0) {
pos = pos - Number(calculateLineWidth(text));
text.setSelectionRange(pos, pos);
} else {
text.setSelectionRange(0, 0);
}
} else {
pos = pos + Number(calculateLineWidth(text));
text.setSelectionRange(pos, pos)
}
})
})
}
moveCursor(text);
#text {
line-height: 1.5;
text-align: justify;
resize: none;
}
#btns {
display: flex;
align-items: center;
}
#mid {
display: flex;
flex-direction: column;
}
.btns {
height: 20px;
}
<textarea id='text' cols="50" rows="6"></textarea>
<div id="btns">
<button id="left" class="btns">←</button>
<div id="mid">
<button id="up" class="btns">🠕</button>
<button id="down" class="btns">🠓</button>
</div>
<button id="right" class="btns">→</button>
</div>