Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to vertically navigate through a textarea using caret position

I'm working on a set of vanilla js functions to navigate an html textarea when the appropriate arrow button is clicked.

for example,

var text = document.getElementById('text');
function larr(){
  text.focus();
  var pos = text.selectionStart;
  pos--
  text.setSelectionRange(pos, pos);
}
<textarea id='text'></textarea>
<button onclick="larr()">&larr;</button>

The left and right functions are simple enough, but I would also like to include up and down arrows. Since each newline can have a different amount of characters, I don't think it is as simple as setting the position forward or back one max-line-length.

I would settle for the arrows taking you to the next or previous line break. I was thinking about splitting the textarea value at the caret position and looping through characters in that direction until a \n is reached, but I can't wrap my head around it.

Does anyone have any suggestions? Thanks!

*IMPORTANT NOTE - this would be for mobile with the native keyboard hidden, so no help from the os! (unless maybe jquery trigger() or execCommand?)

like image 712
Conrad Klek Avatar asked Mar 05 '21 03:03

Conrad Klek


People also ask

How do I change the cursor position in textarea?

To set the cursor at the end of a textarea: Use the setSelectionRange() method to set the current text selection position to the end of the textarea. Call the focus() method on the textarea element. The focus method will move the cursor to the end of the element's value.

What is the caret position?

The CaretPosition interface represents the caret position, an indicator for the text insertion point. You can get a CaretPosition using the Document. caretPositionFromPoint() method.

How do you get a caret position in input?

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. Keep in mind that selectionStart can only be retrieved from the following list of input types: text. password.

How do you move a caret?

Move the caret to the beginning/end of the current word, and then further word by word in the same direction. If Use CamelHumps is selected on the Editor | General | Typing Assistance page of the IDE settings Ctrl+Alt+S , the caret will move to the next/previous capitalized word inside identifiers with CamelCase names.

How to get and set caret position in HTML textarea and textbox?

Here is a working snippet of code to get and set the caret position in HTML textarea and textbox. This code works for IE 6, 7, 8, >=9, Edge, Chrome, Safari, Firefox and Opera. Type the number where you want to set the caret position and click on “Set Position” button. Type the starting character number and end character number.

How do I get the current caret position in word?

Type the starting character number and end character number. Then click on “Set Selection” button to set selection. Click on “Get Position/Selection”. If there is no selection (i.e. selection is collapsed), you will see only single value. This value represents current caret position.

How to get caret position or selection?

How to get caret position or selection Click on “Get Position/Selection”. If there is no selection (i.e. selection is collapsed), you will see only single value. This value represents current caret position.

How to set caret position in AutoCAD?

How to set caret position Type the number where you want to set the caret position and click on “Set Position” button. How to set selection Type the starting character number and end character number. Then click on “Set Selection” button to set selection.


1 Answers

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">&larr;</button>
  <div id="mid">
    <button id="up" class="btns">&#129045;</button>
    <button id="down" class="btns">&#129043;</button>
  </div>
  <button id="right" class="btns">&rarr;</button>
</div>
like image 188
dale landry Avatar answered Nov 15 '22 03:11

dale landry