DEMOs

导航页
#constraint-validation {
  --color-main: #9e9e9e;
  --color-focus: #26a69a;
  --color-error: #f44336;

  width: 80%;

  .inputArea {
    position: relative;
    height: torem(52px);
    margin-top: torem(32px);

    .label {
      position: absolute;
      top: 0;
      left: 0;
      color: var(--color-main);
      font-size: torem(16px);
      line-height: torem(32px);
      transition: 0.2s ease-out;
    }

    .input {
      display: block;
      width: 100%;
      height: torem(32px);
      border: 0;
      border-bottom: 1px solid var(--color-main);
      padding: 0;
      margin-bottom: torem(8px);

      &:focus {
        outline: none;
      }
    }

    .helper {
      display: block;
      color: var(--color-error);
      font-size: torem(12px);
      line-height: torem(12px);
      opacity: 0;
      transition: 0.2s ease-out;
    }

    &.focus {
      .label {
        color: var(--color-focus);
        font-size: torem(12px);
        line-height: torem(16px);
        transform: translateY(torem(-16px));
      }

      .input {
        border-bottom: 1px solid var(--color-focus);
      }
    }

    &.invalid {
      .input {
        border-bottom: 1px solid var(--color-error);
      }

      .helper {
        opacity: 1;
      }
    }
  }

  .button {
    margin-top: torem(32px);

    .submit {
      height: torem(36px);
      color: color(white);
      font-size: torem(14px);
      line-height: torem(36px);
      background-color: var(--color-focus);
      border: 0;
      border-radius: torem(3px);
      padding: 0 torem(16px);
      cursor: pointer;
    }
  }
}
/**
 * 表单聚焦
 */
function focus(e) {
  e.target.closest('.inputArea').classList.add('focus');
}

/**
 * 表单失去焦点
 */
function blur(e) {
  const { value } = e.target;

  if (!value) {
    e.target.closest('.inputArea').classList.remove('focus');
  }
}

/**
 * 表单输入变动
 */
function change(e) {
  const input = e.target;

  input.closest('.inputArea').classList.remove('invalid');

  // customValidity设置后将一直保存,需手动清理,然后执行验证
  if (input.validity.customError) {
    input.setCustomValidity('');
  }

  input.checkValidity();
}

/**
 * 表单invalid处理
 */
function invalid(e) {
  e.preventDefault();

  const input = e.target;
  const inputType = input.getAttribute('type');

  const inputArea = input.closest('.inputArea');
  const helper = inputArea.querySelector('.helper');

  // 自定义相对valueMissing更明确的提示
  if (input.validity.valueMissing) {
    if (inputType === 'email') {
      input.setCustomValidity('Email Required');
    } else if (inputType === 'password') {
      input.setCustomValidity('Password Required');
    }
  }

  // 相对patternMismatch更明确的提示
  if (input.validity.patternMismatch && inputType === 'password') {
    helper.innerHTML = `${input.validationMessage.slice(0, -1)}:${input.title}`;
  } else {
    helper.innerHTML = input.validationMessage;
  }

  inputArea.classList.add('invalid');
}

/**
 * 表单提交,只有完成Constraint Validation才会触发submit事件
 */
function submit(e) {
  e.preventDefault();

  const form = e.target;

  Array.from(form.querySelectorAll('.inputArea')).forEach((elem) => {
    elem.classList.remove('focus', 'invalid');
  });

  form.reset();
}

document.addEventListener('DOMContentLoaded', () => {
  const form = document.querySelector('#constraint-validation');
  form.addEventListener('submit', submit, false);

  Array.from(document.querySelectorAll('.input')).forEach((input) => {
    input.addEventListener('focus', focus, false);
    input.addEventListener('blur', blur, false);

    input.addEventListener('change', change, false);
    input.addEventListener('invalid', invalid, false);
  });
}, false);