Table of Contents

Password Interface

About

This was first written in JQuery for Cure Interactive's first official website, where I wanted to have a more modern system for the password input fields for user login. This login page was never extended to end users but was work that I was proud of. I have since rewritten it in JavaScript.

Examples

Password Input As Normal


Password Input With Feedback And Show Password Control

*iOS has its own Shift and CapsLock indicators in the keyboard so only the "Show Password" control is used on those devices. iOS also has it's own capslock indicator in the input field but it forgets the capslock state whenever the show password button is pressed/released, so that is hidden as well.

Simulating Autofill

Try to prevent people from stealing autofill passwords.

The Code

HTML

<div class="login login-feedback">
  <div class="login__intro">Please enter your username and password:</div>
  <div class="login__input login__username clearfix">
    <label for="username-feedback">Username:</label>
    <div class="login__input__container">
      <input type="username" name="username-feedback" value="" placeholder="Username">
    </div>
  </div>
  <div class="login__input login__password clearfix">
    <label for="password-feedback">Password:</label>
    <div class="login__input__container login__password__icons-container">
      <input class="password__feedback" type="password" name="password-feedback" value="" placeholder="Password">
      <div class="login__password__icons noselect">
        <div class="login__password__icon login__password__icons__shift noselect login__password__icon--hidden" title="Shift key was ON" ></div>
        <div class="login__password__icon login__password__icons__capslock noselect login__password__icon--hidden" title="Caps-Lock is ON!" ></div>
        <div class="login__password__icon login__password__icons__show noselect login__password__icon--hidden" title="Show Password" onclick="return false;">
          <a href="#" title="Show Password">
            <div class="login__password__icon"></div>
          </a>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.login {
  width: 100%;
  max-width: 384px;
  margin: 0 auto;
  background-color: rgba(255,255,255,0.0625);
  -webkit-box-shadow: 0 8px 8px rgba(0,0,0,0.2);
  box-shadow: 0 8px 8px rgba(0,0,0,0.2);
  padding: 16px;
  border-radius: 4px;
}

.login>*:not(:last-child) {
  margin-bottom: 16px;
}

.login__intro {
  text-align: center;
}

.login label {
  text-align: left;
}

.login label,.login input {
  width: 100%;
  height: 32px;
}

.login label {
  display: inline-block;
  margin-bottom: 4px;
}

@media (min-width: 384px) {
  .login__input {
    position: relative;
  }

  .login label {
    float: left;
    display: block;
    width: 96px;
    margin-bottom: 0px;
  }

  .login .login__input__container {
    padding-left: 96px;
  }

  .login input {
    width: 100%;
  }
}

.password__feedback:not(:-moz-placeholder-shown) {
  font-family: monospace;
  letter-spacing: 0.15em;
}

.password__feedback:not(:-ms-input-placeholder) {
  font-family: monospace;
  letter-spacing: 0.15em;
}

.password__feedback:not(:placeholder-shown) {
  font-family: monospace;
  letter-spacing: 0.15em;
}

.login__password__icons-container {
  position: relative;
}

.login__password__icons {
  position: absolute;
  right: 0;
  bottom: 0;
  height: 100%;
  pointer-events: none;
}

.login__password__icon {
  display: inline-block;
  height: 100%;
  background-position: center;
  background-repeat: no-repeat;
}

.login__password__icon--hidden {
  display: none;
}

.login__password__icons>*:not(:last-child) {
  margin-right: 8px;
}

.login__password__icons__show {
  margin-left: -8px;
}

.login__password__icons__show a div {
  width: 40px;
  height: 100%;
  pointer-events: auto;
}

.login__password__icons__show a {
  display: inline-block;
  height: 100%;
  line-height: 0;
}

.login__password__icons__shift,.login__password__icons__capslock {
  width: 17px;
}

.login.login-feedback input[type=password]::-ms-reveal,.login.login-feedback input[type=text]::-ms-clear {
  display: none;
}

input[type="password"]::-webkit-credentials-auto-fill-button,input[type="password"]::-webkit-caps-lock-indicator {
  display: none;
  content: none;
}

login__password__icons__show {
  -webkit-transition-property: opacity;
  transition-property: opacity;
  -webkit-transition-timing-function: ease-in-out;
  transition-timing-function: ease-in-out;
  -webkit-transition-duration: 500ms;
  transition-duration: 500ms;
}

.login__password__icons__show,.login__password__icons__show:link,.login__password__icons__show:visited,.login__password__icons__show:hover,.login__password__icons__show:focus,.login__password__icons__show:active {
  cursor: pointer;
  opacity: 1;
  -webkit-transition-duration: 100ms;
  transition-duration: 100ms;
}

.no-touchevents .login__password__icons__show:hover {
  opacity: 0.65;
}

html:not(.touchscrolled) .login__password__icons__show:active,.no-touchevents .login__password__icons__show:active,.login__password__icons__show.login__password__icons__show--active {
  opacity: 0.4 !important;
}

.login__password__icons .login__password__icons__shift {
  background-image: url("/images/interface/password/shift.png");
}

.login__password__icons .login__password__icons__capslock {
  background-image: url("/images/interface/password/capslock.png");
}

.login__password__icons .login__password__icons__show a div {
  background-image: url("/images/interface/password/show.png");
}

@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2 / 1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
  .login__password__icon {
    background-size: 100%;
  }

  .login__password__icons .login__password__icons__shift {
    background-image: url("/images/interface/password/shift.x2.png");
  }

  .login__password__icons .login__password__icons__capslock {
    background-image: url("/images/interface/password/capslock.x2.png");
  }

  .login__password__icons .login__password__icons__show a div {
    background-image: url("/images/interface/password/show.x2.png");
    background-size: 75%;
  }
}

JavaScript

// classes.js

/**
 * Returns whether the given element has the given class.
 *
 * @param {Element} element
 * @param {string} className
 * @returns {boolean}
 */
function hasClass(element, className) {
	className = ' ' + className + ' ';
	return (' ' + element.className + ' ').replace(/[\n\t]/g, ' ').indexOf(className) > -1
}

function addClass(elements, className) {
  if ( elements.length === undefined ) elements = [elements];
  for (var i = 0; i < elements.length; i++) {
    var element = elements[i];
    if (!hasClass(element, className)) {
      if (element.classList) {
        element.classList.add(className);
      } else {
        element.className = ( element.className + ' ' + className ).replace( /\s+/g, ' ');
      }
    }
  }
}

function removeClass(elements, className) {
  if ( elements.length === undefined ) elements = [ elements ];
  for (var i = 0; i < elements.length; i++) {
    var element = elements[i];
    if (hasClass(element, className)) {
      if (element.classList) {
        element.classList.remove(className);
      } else {
        element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
      }
    }
  }
}

// password_interface.js

function updatePasswordInput(e, isUserInvoked, isKeyEvent, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow) {
  var space = 0;

  if ( iconShift )
  {
    if ( isUserInvoked && isKeyEvent && e.getModifierState('Shift') == true )
    {
      removeClass(iconShift, 'login__password__icon--hidden');
      space += iconShift.offsetWidth + parseInt( ( iconShift.currentStyle || window.getComputedStyle(iconShift) ).marginRight );
    }
    else addClass(iconShift, 'login__password__icon--hidden');
  }

  if ( iconCapslock )
  {
  	if ( isUserInvoked && isKeyEvent && e.getModifierState('CapsLock') == true )
    {
      removeClass(iconCapslock, 'login__password__icon--hidden');
      space += iconCapslock.offsetWidth + parseInt( ( iconCapslock.currentStyle || window.getComputedStyle(iconShift) ).marginRight );
    }
    else addClass(iconCapslock, 'login__password__icon--hidden');
  }

  if ( !isKeyEvent ) updatePasswordVisibility( false, false, passwordInput, iconShow );

  if ( passwordInput.value )
  {
    // addClass(passwordInput, 'password__feedback--filled');
    if ( passwordInput.interacted && isUserInvoked )
    {
      removeClass(iconShow, 'login__password__icon--hidden');
      space += iconShow.offsetWidth + (space ? parseInt( ( iconCapslock.currentStyle || window.getComputedStyle(iconShift) ).marginLeft ) : 0) - 8;
    }
  }
  else
  {
    // removeClass(passwordInput, 'password__feedback--filled');
    if ( isUserInvoked ) addClass(iconShow, 'login__password__icon--hidden');
  }

  // modify inside of text input:
  // passwordInput.style.paddingRight = space + (space ? 8 : 0) + 'px';

  // modify outside of text input:
  passwordContainer.style.paddingRight = space + (space ? 8 : 0) + 'px';

  // keep caret visible while shifting right border:
  if( passwordInput == document.activeElement )
    passwordInput.setSelectionRange(passwordInput.selectionStart, passwordInput.selectionEnd);
}

function updatePasswordVisibility( e, show, passwordInput, iconShow ) {
  if ( show && passwordInput.interacted )
  {
    if ( e ) e.preventDefault();
    if ( passwordInput.type !== 'text' )
    {
      passwordInput.type = 'text';
      addClass(iconShow, 'login__password__icons__show--active');
    }
  }
  else
  {
    if ( passwordInput.type !== 'password' ) passwordInput.type = 'password';
    removeClass(iconShow, 'login__password__icons__show--active');
  }
}

let isIOS = (/iPad|iPhone|iPod/.test(navigator.platform) ||
  (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
  !window.MSStream;

function setupPasswordInterface(passwordContainer) {
  var passwordInput     = passwordContainer.getElementsByTagName('input')[0];

  if ( !passwordInput || passwordInput.type !== 'password' ) return;

  passwordInput.addEventListener('copy', function(e) { e.preventDefault(); }); // prevent people from stealing autofill passwords :)
  passwordInput.addEventListener('cut',   function(e) { e.preventDefault(); });

  // if(!isIOS) {
  var iconShift    = isIOS ? false : passwordContainer.getElementsByClassName('login__password__icons__shift')[0];
  var iconCapslock = isIOS ? false : passwordContainer.getElementsByClassName('login__password__icons__capslock')[0];
  var iconShow     = passwordContainer.getElementsByClassName('login__password__icons__show')[0];

  passwordInput.addEventListener('focusin',   function(e) { // prevent people from stealing autofill passwords :)
    if ( !this.interacted ) {
      passwordInput.value = '';
      this.interacted = true;
      // removeClass(passwordInput, 'password__feedback--filled');
    }
  });
  passwordInput.addEventListener('focusout',  function(e) {
    updatePasswordVisibility( e, false, passwordInput, iconShow );
    updatePasswordInput( e, true, false, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow );
  });
  passwordInput.addEventListener('mousedown', function(e) { updatePasswordInput( e, true, true, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow ); });
  passwordInput.addEventListener('keydown',   function(e) { updatePasswordInput( e, true, true, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow ); });
  passwordInput.addEventListener('keyup',     function(e) { updatePasswordInput( e, true, true, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow ); });
  // }

  iconShow.addEventListener('mousedown',  function(e) { updatePasswordVisibility( e, true, passwordInput, iconShow ); });
  iconShow.addEventListener('mouseup',    function(e) { updatePasswordVisibility( e, false, passwordInput, iconShow ); });
  iconShow.addEventListener('mouseleave', function(e) { updatePasswordVisibility( e, false, passwordInput, iconShow ); });

  iconShow.addEventListener('touchstart', function(e) { updatePasswordVisibility( e, true, passwordInput, iconShow ); });
  iconShow.addEventListener('touchend',   function(e) { updatePasswordVisibility( e, false, passwordInput, iconShow ); });
  iconShow.addEventListener('touchleave', function(e) { updatePasswordVisibility( e, false, passwordInput, iconShow ); });

  updatePasswordInput( false, false, false, passwordContainer, passwordInput, iconShift, iconCapslock, iconShow );
}

function setupPasswordInterfaces(className) {
  var passwordContainers = document.getElementsByClassName(className);

  for (var i = 0; i < passwordContainers.length; i++) {
    var passwordContainer = passwordContainers[i];

    setupPasswordInterface(passwordContainer);
  }
}

setupPasswordInterfaces('login__password__icons-container');

Special Thanks

This page is comprised of my own additions and either partially or heavily modified elements from the following source(s):