Get the coordinates (X, Y) of the last letter of a textarea

4

I have a dropdown that I want to position in the last character of textarea , but I do not know how to achieve it, I researched how to capture the cursor position of textarea but I have not found anything useful and I have no idea how to do it

This is my code that I have so far:

$(function(){
  $('textarea').on('keyup',function(evt){
    if(evt.which >= 96 && evt.which < 106){
      //console.log(String.fromCharCode(evt.which-48))
    }
    //console.log($('textarea').val());
    if($('div.dropdown').hasClass('open')){
      $('div.dropdown').removeClass('open');
    }else{
      $('div.dropdown').addClass('open');
    }
  })
});
textarea{
  border: 1px solid gray;
  height: 100px;
  min-width: 100%;
  max-width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

<textarea></textarea>
<div class="dropdown open">
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>

My expected result is this:

    
asked by JuankGlezz 16.11.2016 в 20:19
source

2 answers

2

textareaHelper is a jQuery plugin that could solve your problem

Here is an example of how it works:

$('textarea').on('keyup paste cut mouseup', function () {
  var $textarea = $(this),
      $dropdown = $('div.dropdown');
  
  // Obtenemos el alto del textarea
  var contentHeight = $textarea.textareaHelper('height');
  
  // Seteamos el alto del contenido, ya que vamos a agrandarlo a medida que escribimos
  $textarea.height(contentHeight);
  
  // Movemos el dropdown justo despues del textarea
  $dropdown.insertAfter($textarea);
  
  // Posicionamos el dropdown a las coords del caret
  $dropdown.css(
    $(this).textareaHelper('caretPos')
  );
});
.textareaHelperContainer {
  position: relative;
}

.textareaHelperContainer .dropdown {
  position: absolute;
  margin-top: 15px; /* apenas mayor al font-size */
  margin-left: 2px; /* una pequeña separacion */
}

textarea {
  width: 250px;
  min-height: 100px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<script type="text/javascript" src="//code.jquery.com/jquery-1.8.3.js"></script>
<script type="text/javascript" src="http://rawgithub.com/Codecademy/textarea-helper/master/textarea-helper.js"></script>

<div class="textareaHelperContainer">
  <textarea></textarea>
</div>
<div class="textareaHelperContainer">
  <textarea></textarea>
</div>

<div class="dropdown open">
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>
    
answered by 16.11.2016 / 20:53
source
1

Adapted from this answer in SOen which complies with what I really look for and gives me the result that I really look for .

What you do is create a copy of the styles of the textarea in a div which the cursor of the div would be a span

Original code on GitHub

My code would look like this:

$(function(){
  $('textarea').on('keyup',function(evt){
    var pos = getCaretCoordinates(this,this.selectionEnd);
    $('div.dropdown').css({
      'position': 'absolute',
      'top': this.offsetTop - this.scrollTop + 15 + pos.top + 'px',
      'left': this.offsetLeft - this.scrollLeft + pos.left + 'px'
    });
    
  });
  

});
var properties = [
  'boxSizing',
  'width',
  'height',
  'overflowX',
  'overflowY',
  
  'borderTopWidth',
  'borderRightWidth',
  'borderBottomWidth',
  'borderLeftWidth',
  
  'paddingTop',
  'paddingRight',
  'paddingBottom',
  'paddingLeft',
  
  'fontStyle',
  'fontVariant',
  'fontWeight',
  'fontStretch',
  'fontSize',
  'lineHeight',
  'fontFamily',
  
  'textAlign',
  'textTransform',
  'textIndent',
  'textDecoration',
  
  'letterSpacing',
  'wordSpacing'
];

var isFirefox = !(window.mozInnerScreenX == null);
var mirrorDiv, computed, style;

getCaretCoordinates = function (element, position) {
  // clon del textarea en forma de div
  mirrorDiv = document.getElementById(element.nodeName + '--mirror-div');
  if (!mirrorDiv) {
    mirrorDiv = document.createElement('div');
    mirrorDiv.id = element.nodeName + '--mirror-div';//TEXTAREA--mirror-div
    document.body.appendChild(mirrorDiv);
  }
  
  style = mirrorDiv.style;
  computed = getComputedStyle(element);//obtenemos las propiedades del textarea
  
  // estilos por default
  style.whiteSpace = 'pre-wrap';
  style.wordWrap = 'break-word';  // only for textarea-s
  
  // position off-screen
  style.position = 'absolute';  // required to return coordinates properly
  style.top = element.offsetTop + parseInt(computed.borderTopWidth) + 'px';
  style.left = element.offsetLeft + parseInt(computed.borderLeftWidth) + 'px';
  style.visibility = 'hidden';  // no necesitamos que sea visible
  
  // transferir las propiedades al div
  properties.forEach(function (prop) {
    style[prop] = computed[prop];
  });
  
  if (isFirefox) {
    style.width = parseInt(computed.width) - 2 + 'px'  
    // Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
    // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
    if (element.scrollHeight > parseInt(computed.height))
      style.overflowY = 'scroll';
  } else {
    style.overflow = 'hidden';  
    // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
  }  
  
  mirrorDiv.textContent = element.value.substring(0, position);
  if (element.nodeName === 'INPUT')
    mirrorDiv.textContent = mirrorDiv.textContent.replace(/\s/g, "\u00a0");
  
  var span = document.createElement('span');
  span.textContent = element.value.substring(position) || '.';  
  // obtenemos los valores del textarea y los escribimos en caso que se encuentre texto por delante dentro del span para obtener la posision exacta
  span.style.backgroundColor = "lightgrey";
  mirrorDiv.appendChild(span);
  
  var coordinates = {
    top: span.offsetTop + parseInt(computed['borderTopWidth']),
    left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
  };
  
  return coordinates;
}
textarea{
  border: 2px solid gray;
  height: 100px;
  min-width: 100%;
  max-width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

<textarea></textarea>
<textarea></textarea>
<div class="dropdown open">
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>
    
answered by 17.11.2016 в 01:59