javascript – How to make a Textarea act like a password field-ThrowExceptions

Exception or error:

I am trying to make a textarea look like a password field. In webkit browsers I have found below attribute to make it

-webkit-text-security : circle    

But it does not work in other browsers. Any cross browser alternative for this…?

How to solve:

You can use JS for this, define a variable and store textarea value in that and replace contents in textarea with any special character. You ‘ll have actual value in JS variable which could be used to submit the form or validate etc.

For this, you can write a function for keydown event.

###

You can use a jQuery plugin like : http://blog.decaf.de/2009/07/iphone-like-password-fields-using-jquery/

###

It can be done, but the implementation is not trivial. The idea is to use an invisible password=true input element as a state/proxy for the password-masked version of your textarea, and use JS to switch the password-masked (fake) textarea with a real textarea on show/hide.

Something to note: a comment by intgr on a similar question states,

Unfortunately this gets ugly very quickly. A normal password field
also allows modifications in the middle, copy-cut-paste, drag & drop,
and probably other behaviors that I couldn’t think of. Emulating all
of these faithfully, even if possible, probably would not be worth the
trouble.

My implementation avoids this complexity by programmatically disabling text navigation via click or arrow keys in the password-masked (fake) textarea. The real textarea can be navigated and edited within however, so there’s very little lost in terms of UX.

Check out my implementation below:

// element handles
const inp = document.getElementById("input-el");
const fta = document.getElementById("fake-textarea-el");
const ftac = document.getElementById("fake-textarea-el-content");
const rta = document.getElementById("real-textarea-el");
const toggle = document.getElementById("pw-toggle");

// initial text element height
const initialHeightPx = fta.clientHeight + 'px';

// show/hide content flag
let hideContent = true;

// util for minimum textarea height workaround
function textWidth(text) {
  const tag = document.createElement('div');
  tag.classList.add('hidden');
  tag.style.whiteSpace = 'nowrap';
  tag.innerHTML = text;

  document.body.appendChild(tag);
  const result = tag.clientWidth;
  
  document.body.removeChild(tag);
  
  return result;
}

function setTextArea(content = '') {
  // update all text elements
  ftac.innerHTML = content.replace(/./g, '•');
  inp.value = content;
  rta.value = content;

  // workaround to use CSS height for minimum
  // textarea height
  let newHeight = initialHeightPx;
  if (textWidth(content) > rta.clientWidth) {
    rta.style.height = 'auto';
    newHeight = rta.scrollHeight + 'px';
  }
  rta.style.height = newHeight;
}

function handleInput(e) {
  const {target: {value}} = e;
  setTextArea(value);
}

inp.oninput = handleInput;
rta.oninput = handleInput;

fta.onfocus = e => {
  inp.focus();
  
  fta.classList.add('has-focus');
  ftac.classList.remove('has-selectall');
}

inp.onblur = e => {
  // deselect input contents
  inp.selectionStart = inp.selectionEnd = -1;
  
  fta.classList.remove('has-focus');
  ftac.classList.remove('has-selectall');
}

inp.onkeydown = e => {
  const {charCode, keyCode, target: {value}} = e;
  const code = keyCode || charCode;
  
  // prevent arrow keys
  if (code >= 37 && code <= 40) {
    e.preventDefault();
  }
  
  // handle ctrl/meta key combinations
  if ((e.metaKey || e.ctrlKey)) {
    switch (e.key) {
      case 'a':
        e.preventDefault();
        inp.setSelectionRange(0, inp.value.length)
        fta.classList.remove('has-focus');
        ftac.classList.add('has-selectall');
        break;
      default:
    }
  } else if (ftac.classList.contains('has-selectall')) {
    fta.classList.add('has-focus');
    ftac.classList.remove('has-selectall');
  }
}

// handle show/hide toggle
toggle.onclick = e => {
  hideContent = !hideContent;
  
  if (hideContent) {
    rta.classList.add('hidden');
    fta.classList.remove('hidden');
    toggle.innerHTML = 'Show';
  } else {
    fta.classList.add('hidden');
    rta.classList.remove('hidden');
    toggle.innerHTML = 'Hide';
  }
}
/* SETUP BEGIN */
* {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

main {
  align-items: center;
  display: flex;
  height: 100vh;
  justify-content: center;
}

button {
  height: 24px;
  margin: 18px;
  width: 60px;
}
/* SETUP END */


#fake-textarea-el, #real-textarea-el {
  border: none; /* remove user-agent textarea border */
  box-shadow: 0 0 0 1px black;
  box-sizing: border-box;
  display: block;
  height: 24px;
  line-height: 18px;
  margin: 0;
  padding: 2px;
  resize: none;
  width: 150px;
  word-wrap: break-word;
}

#fake-textarea-el {
  height: auto;
  min-height: 24px;
}

.hidden {
  position: absolute;
  bottom: 0px;
  z-index: -1000;
  opacity: 0;
  pointer-events: none;
}

.has-selectall {
  /* default selection bg on MacOS + Chrome */
  background: #ACCEF7;
}

.cursor {
  border-right: 1px solid transparent;
  height: 1rem;
}

.has-focus .cursor {
  animation: cursorBlink 1.14s forwards infinite;
}

@keyframes cursorBlink {
  0% {
    border-color: transparent;
  }
  50% {
    border-color: transparent;
  }
  51% {
    border-color: black;
  }
  100% {
    border-color: black;
  }
}
<main>
  <input id="input-el" class="hidden" type="password" />
  <div id="fake-textarea-el" contenteditable>
    <span id="fake-textarea-el-content"></span><span class="cursor"></span>
  </div>
  <textarea id="real-textarea-el" class="hidden"></textarea>
  <button id="pw-toggle">Show</button>
</main>

Leave a Reply

Your email address will not be published. Required fields are marked *