用JavaScript实现表单约束验证
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <base target="_blank" /> <title>三十客 - 用JavaScript实现表单约束验证</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-size: 112.5%; margin-left: auto; margin-right: auto; max-width: 40em; width: 88%; } label { display: block; font-weight: bold; margin-bottom: 0.5em; } .label-normal { font-weight: normal; } .description-date { color: #808080; font-size: 0.8em; font-weight: normal; } .supports-date .description-date { display: none; } input, elect { display: inline-block; font-size: 1em; margin-bottom: 1em; padding: 0.25em 0.5em; width: 100%; } [type="checkbox"],[type="radio"] { margin-bottom: 0.5em; width: auto; } .button { background-color: #0088cc; border: 1px solid #0088cc; border-radius: 1px; color: #ffffff; display: inline-block; font-size: 0.9375em; font-weight: normal; line-height: 1.2; margin-right: 0.3125em; margin-bottom: 0.3125em; padding: 0.5em 0.6875em; width: auto; } .button:active,.button:focus,.button:hover { background-color: #005580; border-color: #005580; color: #ffffff; text-decoration: none; } .button:active { box-shadow: inset 0 0.15625em 0.25em rgba(0, 0, 0, 0.15), 0 1px 0.15625em rgba(0, 0, 0, 0.05); } .error { border-color: red; } .error-message { color: red; font-style: italic; margin-bottom: 1em; } </style> </head> <body> <form class="validate"> <div> <label for="text">姓名:</label> <input type="text" id="text" required> </div> <div> <label for="email">邮件:</label> <input type="email" id="email" title="The domain portion of the email address is invalid (the portion after the @)." pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$" required> </div> <div> <label for="url">网站(URL):</label> <input type="url" id="url" title="The URL is a missing a TLD (for example, .com)." pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$" required> </div> <div> <strong>性别:</strong> <label class="label-normal"> <input type="radio" name="radio" id="radio-1" required>男 </label> <label class="label-normal"> <input type="radio" name="radio" id="radio-2" required>女 </label> </div> <input type="submit" class="button" value="Submit"> </form> <script> var forms = document.querySelectorAll('.validate'); for (var i = 0; i < forms.length; i++) { forms[i].setAttribute('novalidate', true); } var hasError = function (field) { if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return; var validity = field.validity; if (validity.valid) return; if (validity.valueMissing) return '必须输入的字段。'; if (validity.typeMismatch) { if (field.type === 'email') return '必须输入正确格式的电子邮件。'; if (field.type === 'url') return '必须输入正确格式的URL。'; } if (validity.tooShort) return '至少输入 ' + field.getAttribute('minLength') + ' 个字符。当前 ' + field.value.length + ' 个字符。'; if (validity.tooLong) return '至多输入 ' + field.getAttribute('maxLength') + ' 个字符。当前 ' + field.value.length + ' 个字符。'; if (validity.badInput) return '必须输入合法的数字。'; if (validity.stepMismatch) return '必须输入合法的值。'; if (validity.rangeOverflow) return '必须输入小于 ' + field.getAttribute('max') + '的值。'; if (validity.rangeUnderflow) return '必须输入大于 ' + field.getAttribute('min') + '的值。'; if (validity.patternMismatch) { if (field.hasAttribute('title')) return field.getAttribute('title'); return '必须输入指定格式的值。'; } return '必须输入指定合规的值。'; }; var showError = function (field, error) { field.classList.add('error'); if (field.type === 'radio' && field.name) { var group = document.getElementsByName(field.name); if (group.length > 0) { for (var i = 0; i < group.length; i++) { if (group[i].form !== field.form) continue; group[i].classList.add('error'); } field = group[group.length - 1]; } } var id = field.id || field.name; if (!id) return; var message = field.form.querySelector('.error-message#error-for-' + id ); if (!message) { message = document.createElement('div'); message.className = 'error-message'; message.id = 'error-for-' + id; var label; if (field.type === 'radio' || field.type ==='checkbox') { label = field.form.querySelector('label[for="' + id + '"]') || field.parentNode; if (label) { label.parentNode.insertBefore( message, label.nextSibling ); } } if (!label) { field.parentNode.insertBefore( message, field.nextSibling ); } } field.setAttribute('aria-describedby', 'error-for-' + id); message.innerHTML = error; message.style.display = 'block'; message.style.visibility = 'visible'; }; var removeError = function (field) { field.classList.remove('error'); field.removeAttribute('aria-describedby'); if (field.type === 'radio' && field.name) { var group = document.getElementsByName(field.name); if (group.length > 0) { for (var i = 0; i < group.length; i++) { if (group[i].form !== field.form) continue; group[i].classList.remove('error'); } field = group[group.length - 1]; } } var id = field.id || field.name; if (!id) return; var message = field.form.querySelector('.error-message#error-for-' + id + ''); if (!message) return; message.innerHTML = ''; message.style.display = 'none'; message.style.visibility = 'hidden'; }; document.addEventListener('blur', function (event) { if (!event.target.form.classList.contains('validate')) return; var error = hasError(event.target); if (error) { showError(event.target, error); return; } removeError(event.target); }, true); document.addEventListener('submit', function (event) { if (!event.target.classList.contains('validate')) return; var fields = event.target.elements; var error, hasErrors; for (var i = 0; i < fields.length; i++) { error = hasError(fields[i]); if (error) { showError(fields[i], error); if (!hasErrors) { hasErrors = fields[i]; } } } if (hasErrors) { event.preventDefault(); hasErrors.focus(); } }, false); </script> </body> </html>