延迟HTML5的:invalid伪类直到第一个事件

93

我最近发现:invalid伪类会立即应用于required表单元素,即使在页面加载时。例如,如果你有以下代码:

<style>
input:invalid { background-color: pink; color: white; }
input:valid { background-color: white; color: black; }
</style>
…
<input name="foo" required />

然后您的页面将显示一个空的粉色输入框。内置HTML5验证非常好,但我认为大多数用户不希望在他们有机会输入任何值之前进行表单验证。是否有任何方法可以延迟伪类的应用,直到第一个影响该元素的事件(表单提交、失去焦点、更改或其他适当的事件)?是否可以在不使用JavaScript的情况下实现这一点?


我无法评论,因此我会在@user3284463的补充中添加以下内容。makeDirty不应该切换脏类,而应该在未来(蓝色,无效,有效)事件上保持它。函数makeDirty(e){ if (!e.target.classList.contains('dirty')) e.target.classList.add('dirty'); } - Abir Stolov
理想情况下,我希望CSS允许在表单元素上使用:visited来实现这一点。 - matty
14个回答

48

http://www.alistapart.com/articles/forward-thinking-form-validation/

由于我们只想在一个字段获得焦点后才表示它无效,因此我们使用焦点伪类来触发无效的样式。(显然,从一开始标记所有必填字段为无效将是一个不好的设计选择。)

按照这个逻辑,您的代码将看起来像这样...

<style>
    input:focus:required:invalid {background-color: pink; color: white;}
    input:required:valid {background-color: white; color: black; }
</style>

在这里创建了一个范例:http://jsfiddle.net/tbERP/

正如你所想象的,并且从这个范例中也可以看到,这种技术只有在元素获得焦点时才显示验证样式。一旦你把焦点移开,无论它是否有效,样式都会被删除。这绝对不是理想情况。


19
“并不理想。”确实。在元素失去焦点后,是否有一种方式可以应用样式于有效但“非必需”的内容?我还没有想出一种方法。 - Donald Jenkins
1
我可以想象一种(目前不存在的)表单伪类,比如“已提交”,这样它就可以作为任何包含输入的父选择器,例如#myform:submitted input:required:invalid。因为即使当前情况确实不直观,但不能指望CSS知道在特定情况下字段无效(只要它知道,为什么不立即执行呢?),所以您真正想要的是将输入样式与表单状态绑定在一起,而不是与输入本身绑定(例如,当必填字段为空且父表单已提交时)。 - Anthony
这个答案:https://dev59.com/vVgR5IYBdhLWcg3wLq5n 实际上很好地解决了这个问题,即使它需要一点JavaScript。它只是向表单添加一个类“submitted”(就像我建议的那样!),以便样式规则可以是form.submitted input:required:invalid - Anthony
此外,为了完全穷尽这一点,其他 :valid:invalid 输入类型应该存在相同的问题。如果输入类型为 integer 并且没有填写任何内容,则应将其呈现为无效,因为空白不是有效的数字值。唯一之所以这不是问题,是因为在 required 属性之外,空值被认为是有效值。 - Anthony
当单击提交按钮时,每次只会突出显示一个无效字段。通常情况下,如果所有无效字段都突出显示,用户可以在点击提交之前一次性更正它们,这会更方便。是否最好从所有字段中删除必填标记,然后编写自定义JS函数以进行提交? - Aseem

45

这些答案已经过时了。现在你可以通过使用CSS检查占位符伪类来执行此操作。

input:not(:placeholder-shown):invalid {
    background-color: salmon;
}
form:invalid button {
    background-color: salmon;
    pointer-events: none;
}
<form>
    <input type="email" placeholder="me@example.com" required>
    <button type="submit">Submit</button>
</form>

当您输入不完整的电子邮件地址时,它会从正常的背景变成粉色。


4
值得注意的是,如果您关心IE或Edge支持,:placeholder-shown伪类根本不受支持。 https://caniuse.com/#feat=css-placeholder-shown - Leo Napoleon
9
很遗憾,当您唯一的验证方式是 "required" 时,它并不能起作用。当您触摸了一个必填字段或尝试提交带有必填字段的表单时,应该显示错误信息。空的必填字段仍将显示占位符。 - Björn Tantau
我添加了一个示例,根据表单切换提交按钮。 - Carl
这是最好的答案! - Jones G
就此而言,“jon@doe”是一个有效的电子邮件地址 - 域部分不需要包含点。 - Cedric Reichenbach
显示剩余3条评论

27
这在纯CSS中是不可能的,但可以使用JavaScript实现。以下是一个jQuery示例:http://jsfiddle.net/kaleb/9pyMz/

// use $.fn.one here to fire the event only once.
$(':required').one('blur keydown', function() {
  console.log('touched', this);
  $(this).addClass('touched');
});
/**
 * All required inputs initially are yellow.
 */
:required {
  background-color: lightyellow;
}

/**
 * If a required input has been touched and is valid, it should be white.
 */
.touched:required:valid {
  background-color: white;
}

/**
 * If a required input has been touched and is invalid, it should be pink.
 */
.touched:required:invalid {
  background-color: pink;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
  <label>
    Name:
    <input type="text" required> *required
  </label>
</p>
<p>
  <label>Age:
    <input type="text">
  </label>
</p>


那是一个简单的解决方案 ;) - fegemo
3
这是我喜欢的解决方案,但您不再需要jQuery。这可以使用简单的Javascript完成。 - micah

5
Mozilla 通过自己的 :-moz-ui-invalid 伪类来解决这个问题,该伪类仅适用于与表单交互后的情况。由于缺乏支持,MDN 不建议使用它。但是,您可以对 Firefox 进行修改。
有一个第四级规范 :user-invalid 即将推出,它将提供类似的行为。

Firefox不再需要供应商前缀。 - Mark Fisher

5

这是一个使用VanillaJS(不需要jQuery)的版本,基于kzh答案

{
  let f = function() {
    this.classList.add('touched')
  }
  document
    .querySelectorAll('input')
    .forEach((e) => {
      e.addEventListener('blur', f, false)
      e.addEventListener('keydown', f, false)
    })
}
/**
 * All required inputs initially are yellow.
 */
:required {
  background-color: lightyellow;
}

/**
 * If a required input has been touched and is valid, it should be white.
 */
.touched:required:valid {
  background-color: white;
}

/**
 * If a required input has been touched and is invalid, it should be pink.
 */
.touched:required:invalid {
  background-color: pink;
}
<p><label>
  Name:
  <input type="text" required> *required
</label></p>
<p><label>Age:
  <input type="text">
</label></p>


2
虽然这是一个很好的解决方案,但你可能想要将 querySelectorAll 替换为 getElementsByTagName,因为在我的机器上它快了大约 17000 倍!自己测试一下:https://jsperf.com/queryselectorall-vs-getelementsbytagname - Boltgolt
1
@Boltgolt 提醒大家:过早地进行优化总是一个坏主意,特别是对于运行在微秒级别的东西。是的,querySelectorAll需要38μs,而getElementsByTagName只需要9μs,但是当创建这样的表单时,这并不会有什么影响。 - David Mulder
@DavidMulder 在这种情况下我不同意,为什么要坚持使用稍差的解决方案,当用 getElementsByTagName 替换 querySelectorAll 这么容易就能得到更快的解决方案呢?我个人认为这更多是选择合适的工具来完成工作。 - Boltgolt

3
我在我的代码库中创建了一个小的解决方案来处理这个问题。我只需在我的<form/>元素上添加novalidate属性和data-validate-on="blur"属性。这将监视该类型的第一个事件。这样,您仍然可以使用本地的:invalid CSS选择器进行表单样式设置。
$(function () {
    $('[data-validate-on]').each(function () {
        var $form = $(this);
        var event_name = $form.data('validate-on');

        $form.one(event_name, ':input', function (event) {
            $form.removeAttr('novalidate');
        });
    });
});

这对我来说是解决方案。 - robrecord

2

在表单元素提交前,会触发一个名为html5 invalid的事件,用于检测元素是否通过checkValidity检查。你可以利用这个事件来添加一个类,例如将其应用于周围的表单,并在此事件发生后仅显示 :invalid 样式。

 $("form input, form select, form textarea").on("invalid", function() {
     $(this).closest('form').addClass('invalid');
 });

您的CSS代码应该如下所示:

:invalid { box-shadow: none; }
.invalid input:invalid,
.invalid textarea:invalid,
.invalid select:invalid { border: 1px solid #A90909 !important; background-color: #EEC2C2; }

第一行代码移除了默认样式,这样表单元素在页面加载时看起来是中性的。一旦无效事件触发(当用户尝试提交表单时),元素就会明显地呈现为无效状态。

1

agouseh想法之后,您可以使用一些javascript来检测提交按钮何时被关注,并在此时显示验证。

当提交按钮被关注或点击时,javascript将向表单字段添加一个类(例如submit-focussed),这样就可以使用CSS对无效输入进行样式处理。

这遵循最佳实践,即在用户完成填写字段后显示验证反馈,因为根据研究,在过程中显示验证并没有额外的好处。

document
  .querySelector('input[type=submit]')
  .onfocus = function() {
    this
      .closest('form')
      .classList
      .add('submit-focussed');
  };
form.submit-focussed input:invalid {
  border: thin solid red;
}
<form>
  <label>Email <input type="email" required="" /></label>
  <input type="submit" />
</form>

jQuery替代方案

(function($) {
  $('input[type=submit]').on('focus', function() {
    $(this)
      .parent('form')
      .addClass('submit-focussed');
  });
})(jQuery); /* WordPress compatible */

1
一种很好的方法是使用CSS类来抽象“:invalid, :valid”,然后使用一些JavaScript来检查输入字段是否被聚焦。
CSS:
input.dirty:invalid{ color: red; }
input.dirty:valid{ color: green; }

JS:

// Function to add class to target element
function makeDirty(e){
  e.target.classList.toggle('dirty');
}

// get form inputs
var inputs = document.forms[0].elements;

// bind events to all inputs
for(let input of inputs){
  input.addEventListener('invalid', makeDirty);
  input.addEventListener('blur', makeDirty);
  input.addEventListener('valid', makeDirty);
}

演示


1
你可以让具有特定类且为必填项的元素变成粉色。为每个必填元素添加事件处理程序,当离开该元素时添加该类。
类似于以下代码:
<style>
  input.touched:invalid { background-color: pink; color: white; }
  input.touched:valid { background-color: white; color: black; }
</style>
<script>
  document.addEventListener('DOMContentLoaded', function() {
    var required = document.querySelectorAll('input:required');
    for (var i = 0; i < required.length; ++i) {
      (function(elem) {
        function removeClass(name) {
          if (elem.classList) elem.classList.remove(name);
          else
            elem.className = elem.className.replace(
              RegExp('(^|\\s)\\s*' + name + '(?:\\s+|$)'),
              function (match, leading) {return leading;}
          );
        }

        function addClass(name) {
          removeClass(name);
          if (elem.classList) elem.classList.add(name);
          else elem.className += ' ' + name;
        }

        // If you require a class, and you use JS to add it, you end up
        // not showing pink at all if JS is disabled.
        // One workaround is to have the class on all your elements anyway,
        // and remove it when you set up proper validation.
        // The main problem with that is that without JS, you see what you're
        // already seeing, and stuff looks hideous.
        // Unfortunately, you kinda have to pick one or the other.


        // Let non-blank elements stay "touched", if they are already,
        // so other stuff can make the element :invalid if need be
        if (elem.value == '') addClass('touched');

        elem.addEventListener('blur', function() {
          addClass('touched');
        });

        // Oh, and when the form submits, they need to know about everything
        if (elem.form) {
          elem.form.addEventListener('submit', function() {
            addClass('touched');
          });
        };
      })(required[i]);
    }
  });
</script>

当然,这段代码在IE8及以下版本中不会起作用,因为(a) DOMContentLoaded 是相对较新的技术,在IE8发布时并未成为标准,(b) IE8使用attachEvent而不是DOM标准的addEventListener,(c) IE8不支持HTML 5,因此不会关注:required


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接