moonchunkcheese
moonchunkcheese

Reputation: 41

Placeholder that disappears character by character

My goal is to have an input element, with a placeholder that disappears character by character as you type characters into the input. This is best explained using a "birthday" input, with the placeholder MM/DD/YYYY. If you type 06 into the input, the placeholder /DD/YYYY should still remain.

Although this question is answered in: text box with placeholder text that dissapears character by character, I have one more restriction: the "input" must be a "contenteditable="true"" div. The accepted answer there doesn't work with contenteditable divs.

A pure solution that's pure HTML, CSS and JavaScript is best. No jQuery please.

Upvotes: 2

Views: 1712

Answers (1)

Thatkookooguy
Thatkookooguy

Reputation: 7012

Here's my trick:

  • the placeholder sits behind the contenteditable
  • the contenteditable needs to grow based on content size. this is done by setting display: inline-block on the contenteditable.
  • both the parent element and the contenteditable should have the same background color.
  • onclick function is added to parent element to allow focus on editablecontent while it's width is 0%
  • monospace font is used to make sure every character in the font have the same width (making it so that each character you write hides exactly one character)
  • when the contenteditable is at width 0, a fake caret is added to indicate the focus (this is actually inside the placeholder). this is done using the blink animation, with the general sibling combinator (~), the :empty selector and :focus selector
  • the placeholder can also be achieved with an after or before pseudo-element, but since we can't put a pseudo-element inside a contenteditable, using an actual dom element for the placeholder allows use to use the general sibling combinator to handle the contenteditable empty state
  • some browser will show the original caret when the width is 0 (firefox for example). to solve the double caret, we hide the input caret on an empty state

CSS variables are used for syncing colors on all elements nesting inside the main container. I added versions: grey background and white background. Also, this is the only CSS feature used here that is not fully supported by all modern browsers yet. So if you want full browser support, you can ditch that specifically since it's not essential to the result :-)

IE & Edge still show the original caret on the empty state. (I added a function to the javascript to remove the fake caret on these browsers)

// Get IE or Edge browser version
var isIEOrEdge = detectIE();

if (isIEOrEdge) {
  let editables = document.querySelectorAll('.kb-editable');

  for (let i = 0; i < editables.length; i++) {
    editables[i].classList.add('kb-edge');
  }
}

// this is the only function that is actually needed if the double IE\Edge caret doesn't bother you
function getFocus(id) {
  document.getElementById(id).focus();
}



/* VERY OPTIONAL :-) */
/**
 * detect IE
 * returns version of IE or false, if browser is not Internet Explorer
 */
function detectIE() {
  var ua = window.navigator.userAgent;

  // Test values; Uncomment to check result …

  // IE 10
  // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';

  // IE 11
  // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';

  // Edge 12 (Spartan)
  // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';

  // Edge 13
  // ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';

  var msie = ua.indexOf('MSIE ');
  if (msie > 0) {
    // IE 10 or older => return version number
    return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
  }

  var trident = ua.indexOf('Trident/');
  if (trident > 0) {
    // IE 11 => return version number
    var rv = ua.indexOf('rv:');
    return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
  }

  var edge = ua.indexOf('Edge/');
  if (edge > 0) {
    // Edge (IE 12+) => return version number
    return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
  }

  // other browser
  return false;
}
.kb-editable {
  --bg-color: #D3D3D3;
  --placeholder-color: grey;
  --text-color: black;
  --border-color: transparent;
  --padding: 5px;
  width: 200px;
  height: 200px;
  background: var(--bg-color);
  position: relative;
  align-items: start;
  font-family: 'Anonymous Pro', monospace;
  font-size: 14px;
  overflow-y: auto;
  display: inline-block;
  cursor: text;
  border: 1px solid var(--border-color);
  padding: var(--padding);
}

.kb-editable [contenteditable="true"] {
  position: relative;
  z-index: 2;
  background: var(--bg-color);
  color: var(--text-color);
  outline: none;
  max-width: 100%;
  max-height: 100%;
  display: inline-block;
  /* deal with long words (break them to multiple lines) */
  word-wrap: break-word;
}

.kb-editable .kb-placeholder {
  position: absolute;
  top: var(--padding);
  bottom: var(--padding);
  left: var(--padding);
  right: var(--padding);
  color: var(--placeholder-color);
}


/* used for non-chrome to hide original caret on empty state */
[contenteditable="true"]:focus:empty {
  color: transparent;
  text-shadow: 0 0 0 black;
}

.kb-editable:not(.kb-edge) [contenteditable="true"]:focus:empty~.kb-placeholder:before {
  content: "|";
  color: var(--text-color);
  position: absolute;
  top: 0;
  left: 0;
  animation: 1s blink step-end infinite;
  caret-color: transparent;
}

@keyframes blink {
  from,
  to {
    color: transparent;
  }
  50% {
    color: black;
  }
}
<link href="https://fonts.googleapis.com/css?family=Anonymous+Pro" rel="stylesheet">

<div class="kb-editable" onclick="getFocus('black')">
  <div contenteditable="true" id="black"></div>
  <span class="kb-placeholder">I'm a placeholder</span>
</div>

<div class="kb-editable" onclick="getFocus('white')" style="--bg-color: white; --border-color: black">
  <div contenteditable="true" id="white"></div>
  <span class="kb-placeholder">I'm the white version!</span>
</div>

Upvotes: 6

Related Questions