How to use square cursor in a HTML input field?
Asked Answered
R

6

29

How can I use this square cursor (image below) in the text <input> tags?

C:\WIKIPEDIA

Risarise answered 21/9, 2010 at 7:9 Comment(0)
C
42

Sample

Sample

I've changed how it works, and it seems to solve a few issues :)

  • Accepts any text a normal input can
  • Backspace works
  • Theoretically can support pasting text

Usual caveats apply still, most notably the inability to visually see where the caret is.

I'd think long and hard whether this solution is worth implementing, based on its drawbacks and usability issues.

$(function() {
  var cursor;
  $('#cmd').click(function() {
    $('input').focus();
    cursor = window.setInterval(function() {
      if ($('#cursor').css('visibility') === 'visible') {
        $('#cursor').css({
          visibility: 'hidden'
        });
      } else {
        $('#cursor').css({
          visibility: 'visible'
        });
      }
    }, 500);

  });

  $('input').keyup(function() {
    $('#cmd span').text($(this).val());
  });

  $('input').blur(function() {
    clearInterval(cursor);
    $('#cursor').css({
      visibility: 'visible'
    });
  });
});
#cmd {
  font-family: courier;
  font-size: 14px;
  background: black;
  color: #21f838;
  padding: 5px;
  overflow: hidden;
}
#cmd span {
  float: left;
  padding-left: 3px;
  white-space: pre;
}
#cursor {
  float: left;
  width: 5px;
  height: 14px;
  background: #21f838;
}
input {
  width: 0;
  height: 0;
  opacity: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="cmd">
  <span></span>
  <div id="cursor"></div>
</div>

<input type="text" name="command" value="" />
Chloromycetin answered 21/9, 2010 at 7:16 Comment(9)
+1 for a cool little example...more caveats come to mind, like support for multiple elements, tab index issues, accessibility, etc.Division
very good solution. Now there are only 2 problems: 1) Only uppercase chars 2) Backspace and Enter doesn't work.Risarise
yeah, it does have some serious limitations. Backspace and Enter could be added. Look for their keycode and submit a form or substr(str, -1). It all depends on how far your willing to go vs how bad you want this kind of input.Chloromycetin
Three more fundamental problems: one, the cursor cannot be placed anywhere other than at the end of the text; two, the keyCode property of a keyup (or keydown) event is not a character code and should not be treated as such. Use only the keypress event for handling text input; and three, this is not a form input so cannot be submitted to the server. You'd need a hidden input that's automatically kept in sync with the content of your div.Greco
@Tim Down See the second revision. It allows any text an input can, and it can be submitted to the server. Though you can't place the cursor in any arbitrary position. See the part about caveats :PChloromycetin
That was me, because as it was before your recent changes I thought it was too flawed to be useful. I've removed my downvote now because you've fixed many of the issues.Greco
With modern browsers you can use the selectionEnd property to determine where the cursor is. I've got a fiddle here that supports arbitrary cursor positioning: jsfiddle.net/HugeHugh/gZMSQReichsmark
this is surprisingly simplerKhan
Is there any way to get the fixed width of a chara so you can move a div according to input.lenght px,em,rem and hide the text caret by caret-color:same-colorasBg;.Firework
A
10

The accepted solution has a number of issues:

  • It's painfully slow, especially when holding down keys.

  • The real caret can be moved with the arrows, but you won't see where it is, as the fake square one will always be at the end.

  • The input needs to be focused again manually by clicking it.

  • It's not possible to properly select text, as the selection is lost as soon as you release the mouse button, and it's completelly impossible to select text using the keyboard.

A better solution might be to use CSS to make sure the "caret" element always comes after a contenteditable "input" and JS to make sure the contenteditable element is always focused. You might try to do this last thing by adding autofocus to the contenteditable element and using a <label> for the caret element, but that doesn't work on contenteditable elements. Note no keyboard event listeners are needed:

const input = document.getElementById('input');
const caret = document.getElementById('caret');

// Move the focus back to the input if it moves away from it:
input.addEventListener('blur', (e) => {  
  input.focus();
});

// Set the focus to the input so that you can start typing straight away:
input.focus();
body {
  background: #000;
  color: #0F0;
  font-family: monospace;
  height: 100vh;
  box-sizing: border-box;
  overflow-x: hidden;
  overflow-y: scroll;
  margin: 0;
  padding: 16px;
}

#input {
  display: inline;
  word-break: break-all;
  outline: none;
  visibility: visible;
}

#caret {
  border: 0;
  padding: 0;
  outline: none;
  background-color: #0F0;
  display: inline-block;
  font-family: monospace;
}
C:\WIKIPEDIA > 

<div id="input" contenteditable="true"></div><button id="caret" for="input">&nbsp;</button>

In a more realistic example, you might want to:

  • Avoid trapping the focus in the contenteditable element, as that would prevent selecting previous commands. Instead, focus to the contenteditable element only once the user presses some key.

  • Show a different caret depending on its position: square if it's at the end of the input, line if it's somewhere else (unless overtype mode is enabled with the Ins key).

  • Add a new command/entry if is pressed.

  • Prevent entering formatted text and automatically split it up into multiple commands/entries when needed.

const history = document.getElementById('history');
const input = document.getElementById('input');
const cursor = document.getElementById('cursor');

function focusAndMoveCursorToTheEnd(e) {  
  input.focus();
  
  const range = document.createRange();
  const selection = window.getSelection();
  const { childNodes } = input;
  const lastChildNode = childNodes && childNodes.length - 1;
  
  range.selectNodeContents(lastChildNode === -1 ? input : childNodes[lastChildNode]);
  range.collapse(false);

  selection.removeAllRanges();
  selection.addRange(range);
}

function handleCommand(command) {
  const line = document.createElement('DIV');
  
  line.textContent = `C:\\WIKIPEDIA > ${ command }`;
  
  history.appendChild(line);
}

// Every time the selection changes, add or remove the .noCursor
// class to show or hide, respectively, the bug square cursor.
// Note this function could also be used to enforce showing always
// a big square cursor by always selecting 1 chracter from the current
// cursor position, unless it's already at the end, in which case the
// #cursor element should be displayed instead.
document.addEventListener('selectionchange', () => {
  if (document.activeElement.id !== 'input') return;
  
  const range = window.getSelection().getRangeAt(0);
  const start = range.startOffset;
  const end = range.endOffset;
  const length = input.textContent.length;
  
  if (end < length) {
    input.classList.add('noCaret');
  } else {
    input.classList.remove('noCaret');
  }
});

input.addEventListener('input', () => {    
  // If we paste HTML, format it as plain text and break it up
  // input individual lines/commands:
  if (input.childElementCount > 0) {
    const lines = input.innerText.replace(/\n$/, '').split('\n');
    const lastLine = lines[lines.length - 1];
    
    for (let i = 0; i <= lines.length - 2; ++i) {
      handleCommand(lines[i]);
    }
  
    input.textContent = lastLine;
    
    focusAndMoveCursorToTheEnd();
  }
  
  // If we delete everything, display the square caret again:
  if (input.innerText.length === 0) {
    input.classList.remove('noCaret');  
  }  
});

document.addEventListener('keydown', (e) => {   
  // If some key is pressed outside the input, focus it and move the cursor
  // to the end:
  if (e.target !== input) focusAndMoveCursorToTheEnd();
});

input.addEventListener('keydown', (e) => {    
  if (e.key === 'Enter') {
    e.preventDefault();
        
    handleCommand(input.textContent);    
    input.textContent = '';
    focusAndMoveCursorToTheEnd();
  }
});

// Set the focus to the input so that you can start typing straigh away:
input.focus();
body {
  background: #000;
  color: #0F0;
  font-family: monospace;
  height: 100vh;
  box-sizing: border-box;
  overflow-x: hidden;
  overflow-y: scroll;
  word-break: break-all;
  margin: 0;
  padding: 16px;
}

#input {
  display: inline;
  outline: none;
  visibility: visible;
}

/*
  If you press the Insert key, the vertical line caret will automatically
  be replaced by a one-character selection.
*/
#input::selection {
  color: #000;
  background: #0F0;
}

#input:empty::before {
  content: ' ';
}

@keyframes blink {
  to {
    visibility: hidden;
  }
}

#input:focus + #caret {
  animation: blink 1s steps(5, start) infinite;
}

#input.noCaret + #caret {
  visibility: hidden;
}

#caret {
  border: 0;
  padding: 0;
  outline: none;
  background-color: #0F0;
  display: inline-block;
  font-family: monospace;
}
<div id="history"></div>

C:\WIKIPEDIA > 

<div id="input" contenteditable="true"></div><button id="caret" for="input">&nbsp;</button>

In general, it's usually a bad idea to listen for keyboard events (keydown / keypress / keyup) to handle text input or cursors, as the value of the input can also be updated by pasting or dropping text into it and there are many edge cases, such as arrows, delete, escape, shortcuts such as select all, copy, paste... so trying to come up with an exhaustive list of all the keys we should take care of is probably not the best approach.

Moreover, that won't work on mobile, where most keys emit the same values e.key = 'Unidentified', e.which== 229 and e.keyCode = 229.

Instead, it's usually better to rely on other events such as input and use KeyboardEvents to handle very specific keys, like in this case.

If you need to check KeyboardEvent's properties values such as e.key, e.code, e.which or e.keyCode you can use https://keyjs.dev. I will add information about these kinds of cross-browser incompatibilities soon!

Key.js \ JavaScript KeyboardEvent's key codes & key identifiers

Disclaimer: I'm the author.

Automobile answered 5/10, 2020 at 22:47 Comment(1)
When using arrows the caret becomes a regular thin line and it acts a bit floppy on ChromeDarien
P
2

AFAIK, that's not possible for html text box, you could style the input itself but you can do nothing about the cursor other than applying the cursor options that are already available :(

Psychosis answered 21/9, 2010 at 7:11 Comment(1)
Thats right. Mouse cursors can be manipulated. Text cursors cannot.Pretty
T
2

you can't. which means: you could do it yourself by using a fixed-with font, use a blinking gif als background which position is set dynamicaly by calculating the with of the already typed text - but there will be the "normal" cursor above your gif, making this solution ugly

Tantalizing answered 21/9, 2010 at 7:17 Comment(0)
B
2

For <input> tags, there's not much you can do. If you didn't mind it being a horrible hack, you could always use JavaScript to resize the text box as needed (set width = *something* * count), and have an <img> with the cursor to the right.

I don't think there are any less 'ugh' solutions, bar handling the text input yourself, which is probably overkill.

Bibi answered 21/9, 2010 at 7:17 Comment(0)
D
0

You would have to 1) roll your own textbox and 2) hide the real cursor by continually focusing it elsewhere. Then, capture the key events at the document/body level, and insert that value into your own element. The cursor would then be an animated GIF that was always positioned at far right of your "textbox".

You will run into issues with #2, and the whole thing is generally inadvisable. HTML 5 opens up some new possibilities, but it's still an awful lot of work for a cursor.

Division answered 21/9, 2010 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.