1.0.1 • Published 4 years ago

text-caret-pos v1.0.1

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

Text Caret Position

MIT license GitHub package.json version npm

Get the top and left coordinates of the caret in a <textarea> or <input type="text">, in pixels. Useful for textarea autocompletes like GitHub or Twitter, or for single-line autocompletes like the name drop-down in Facebook or the company dropdown on Google Finance.

How it's done: a faux <div> is created off-screen and styled exactly like the textarea or input. Then, the text of the element up to the caret is copied into the div and a <span> is inserted right after it. Then, the text content of the span is set to the remainder of the text in the textarea, in order to faithfully reproduce the wrapping in the faux div. The same is done for the input to simplify the code, though it makes no difference.

NOTE: This fork of textarea-caret-position wraps the functionality in an module (UMD) which exposes some more (helper) functions and adds some options (see API description below).

Demo

Check out the JSFiddle (original library) or the test.html.

Features

  • supports <textarea>s and <input type="text"> elements
  • pixel precision
  • RTL (right-to-left) support
  • no dependencies whatsoever
  • browser compatibility: Chrome, Safari, Firefox (despite two bugs it has), Opera, IE9+
  • supports any font family and size, as well as text-transforms
  • the text area can have arbitrary padding or borders
  • not confused by horizontal or vertical scrollbars in the textarea
  • supports hard returns, tabs (except on IE) and consecutive spaces in the text
  • correct position on lines longer than the columns in the text area
  • no problem getting the correct position when the input text is scrolled (i.e. the first visible character is no longer the first in the text)
  • no "ghost" position in the empty space at the end of a line when wrapping long words in a <textarea>

API

Usage Example:

//loading module with require:
//NOTE if no require function is available, the module will be exported
//     to global variable textCaretPos
var textCaretPos = require('textarea-caret');

document.querySelector('textarea').addEventListener('input', function () {
  var coordinates = textCaretPos.getCoordinates(this, this.selectionEnd);
  console.log(coordinates.top);
  console.log(coordinates.left);
  console.log(coordinates.height);
})

createDiv(options) : void create faux DIV - options (Options): OPTIONAL additional options (see below)

measureFontZoom() : number calculate a "font zoom": zoom that is applied "by the environment" and not set by the CSS/HTML itself (e.g. some Android variants allow setting a font zoom in the system settings).
If not "font zoom" is detected, returns 1 by default (i.e. no scaling).

styleDiv(element, position, div, options) : void apply styling of the target-element to the faux-DIV for accurately calculating the coordinates - element (HTMLElement): the target element (textarea or input) - position (number): the character/text index, i.e. position for which the coordinates should be calculated - div (HTMLElement): the faux DIV for calculating the coordinates - options (Options): OPTIONAL additional options (see below)

resetStyleDiv() : void reset styling for faux DIV, i.e. force re-styling for next calculation (only relevant if DIV is reused)

resetDiv(options) : void reset/remove faux DIV, if reuse or debug was enabled
NOTE if faux DIV was created with custom options.id, then the options-argument must contain the same id (otherwise this call will have not effect) - options (Options): OPTIONAL additional options (see below)

updateCoordinates(element, position, div, options) : {top: number, left: number} recalulate the coordinates (e.g. due to changed text) without re-styling the faux DIV - element (HTMLElement): the target element (textarea or input) - position (number): the character/text index, i.e. position for which the coordinates should be calculated - div (HTMLElement): the faux DIV for calculating the coordinates - options (Options): OPTIONAL additional options (see below)

getCoordinates(element, position, options) : {top: number, left: number} get coordinates in the target element for the text position (i.e. index in string) - element (HTMLElement): the target element (textarea or input) - position (number): the character/text index, i.e. position for which the coordinates should be calculated - options (Options): OPTIONAL additional options (see below)

Options: optional settings for calculating the coordinates - options for calculating the caret coordinates: - options.debug BOOLEAN: show shadow DIV that is used for calculating the caret coordinates; this will also include the created DIV in the coordinates-object in property _div (DEFAULT: false) - options.reuse BOOLEAN: reuse shadow DIV that is used for calculating the caret coordinates (DEFAULT: false) - options.returnDiv BOOLEAN: if reuse was enabled, returns the shadow DIV in the coordinates-object in property _div (DEFAULT: false) - options.returnHeight BOOLEAN: returns the caret offset height (instead of computed lineHeight) in the returned coordinates-object in property height (DEFAULT: false) - options.id STRING: the id attribute for the shadow DIV (DEFAULT: "input-textarea-caret-position-mirror-div") - options.guessIfUpdateStyle BOOLEAN | FUNCTION: if TRUE, styling of the shadow DIV is not updated, if the current target element has the same type (Tag Name) as the previous one.
If function: a callback for determining, if the shadow DIV's style should be updated (return TRUE, if it should get updated): callback(shadowDiv) : boolean
NOTE this option is only relevant, if "reuse" is TRUE.
(DEFAULT: false) - options.forceUpdateStyle BOOLEAN: force updating the style of the shadow DIV; only relevant, if "reuse" is TRUE (DEFAULT: false) - options.forceClearFauxStyle BOOLEAN: force faux span to use "cleared" style (e.g. in case SPAN is globally styled) (DEFAULT: false) - options.fauxId STRING: use ID for faux span (e.g. for styling faux span) (DEFAULT: undefined) - options.fontZoom NUMBER | BOOLEAN: apply zoom factor to font-size.
If true (boolean) the zoom factor will be calculated using measureFontZoom(), and the option-value (true) will be replaced with the measured zoom factor.
(DEFAULT: undefined) - options.allowInputWrap BOOLEAN: if TRUE, allows text-wrapping for INPUT elements (note: the W3C specifically states that text in INPUT will not be wrapped, even if styles would "request" it, like "word-wrap: break-word" or "word-break: break-all | break-word" or similar)
(DEFAULT: false) - options.additionalStyles ARRAY: transfers additional styles properties from the target element to the shadow DIV - options.additionalAttributes ARRAY: transfers additional (node) attributes from the target element to the shadow DIV
- options.text STRING | FUNCTION: the text value that should be used for the calculation.
If function: a callback which's return value is used as the text: callback(element, options) : string

var coordinates = getCoordinates(element, position)

position is an integer indicating the location of the caret. You basically pass this.selectionStart or this.selectionEnd. This way, this library isn't opinionated about what the caret is.

coordinates is an object of the form {top: , left: , height: }.

Known issues

  • Tab characters in <textarea>s aren't supported in IE9 (issue #14)

Dependencies

None.

TODO

  • Add tests.
  • Consider adding IE-specific code if it avoids the necessity of creating the mirror div and might fix #14.
  • Test IE8 support with currentStyle.

Implementation notes

For the same textarea of 25 rows and 40 columns, Chrome 33, Firefox 27 and IE9 returned completely different values for computed.width, textarea.offsetWidth, and textarea.clientWidth. Here, computed is getComputedStyle(textarea):

Chrome 33

  • computed.width: "240px" = the text itself, no borders, no padding, no scrollbars
  • textarea.clientWidth: 280 = computed.width + padding-left + padding-right
  • textarea.offsetWidth: 327 = clientWidth + scrollbar (15px) + border-left + border-right

IE 9: scrollbar looks 16px, the text itself in the text area is 224px wide

  • computed.width: "241.37px" = text only + sub-pixel scrollbar? (1.37px)
  • textarea.clientWidth: 264
  • textarea.offsetWidth: 313

Firefox 27

  • computed.width: "265.667px"
  • textarea.clientWidth: 249 - the only browser where textarea.clientWidth < computed.width
  • textarea.offsetWidth: 338

Contributors