【问题标题】:How can I get the html5 validation message to show up without scrolling?如何在不滚动的情况下显示 html5 验证消息?
【发布时间】:2020-01-10 18:51:48
【问题描述】:

我正在尝试自定义 HTML5 验证的行为。当用户点击提交时,我希望无效字段滚动到页面中间。我的第一次尝试是这样做,但它不起作用:

    $.each($("input, select"), function (index, input) {
        input.addEventListener("invalid", function () {
            this.scrollIntoView({
                block: 'center',
                behavior: 'smooth'
            });
        });
    });

https://jsfiddle.net/gib65/j1ar87yq/

Calling scrollIntoView(...) with block center not working

它不起作用,因为默认滚动行为,即立即将无效字段滚动到页面顶部(即不平滑),覆盖了我试图在 scrollIntoView(... ) 选项。

然后我尝试了这个:

    $.each($("input, select"), function (index, input) {
        input.addEventListener("invalid", function (e) {
            e.preventDefault();
            this.scrollIntoView({
                block: 'center',
                behavior: 'smooth'
            });
        });
    });

https://jsfiddle.net/gib65/527u1cm3

添加 preventDefault() 允许我指定的滚动行为发生。但它也阻止了无效字段的聚焦和验证消息的出现。

所以我的问题是:有没有办法让验证消息在不滚动的情况下弹出?

类似这样的:

    $.each($("input, select"), function (index, input) {
        input.addEventListener("invalid", function (e) {
            e.preventDefault();
            this.scrollIntoView({
                block: 'center',
                behavior: 'smooth'
            });
            this.focus();
            this.showValidationMessage();
        });
    });

我尝试过 reportValidity() 但这会触发整个验证过程,这当然会破坏目的(即它将调用覆盖我的自定义滚动的默认滚动)。我也试过 checkValidity() 但这会导致“超出最大调用堆栈大小”,可能是因为它触发了“无效”事件,导致侦听器再次拿起它并无限期重复。

任何帮助将不胜感激。谢谢。

【问题讨论】:

    标签: javascript jquery html validation scroll


    【解决方案1】:

    在 Chrome 上,调用 reportValidity() 时不会发生默认滚动。所以问题只出现在重复调用上,我们总是可以移除监听器并重新绑定它:

    function listener(e) {
       e.preventDefault();
       this.scrollIntoView({
          behavior: 'smooth',
          block: 'center'
       });
       this.focus();
       //removing the listener here
       $("#validateMe")[0].removeEventListener("invalid", listener);
       this.reportValidity();
       $("#validateMe")[0].addEventListener("invalid", listener);
    
    }
    
    $("#validateMe")[0].addEventListener("invalid", listener);
    $("form").submit(function () {
       console.log("submitting");
    });
    

    这有点像 hack……但它会变得更糟

    这在 FireFox 或 Edge 上不会发生,所以我们需要一个不同的解决方案……老实说,我在 Web 上的任何地方都找不到。即使在 HTML5 的官方文档中,也没有单独调用信息气泡的工作。然而,我确实尝试了超时,当事情发生得太快时我总是求助于它:

    function listener(e) {
       e.preventDefault();
       this.scrollIntoView({
          behavior: 'smooth',
          block: 'center'
       });
       //removing the listener here
       $("#validateMe")[0].removeEventListener("invalid", listener);
       setTimeout(() => {
            this.focus();
            this.reportValidity();
            $("#validateMe")[0].addEventListener("invalid", listener);
       }, 500);
    }
    
    $("#validateMe")[0].addEventListener("invalid", listener);
    $("form").submit(function () {
       console.log("submitting");
    });
    

    此解决方案适用于 Chrome 和 Firefox。虽然不是边缘。它也仅取决于滚动的速度和放置的超时。因此,如果您将超时设置为较低的值,则错误消息将提前弹出并在浏览器完成滚动到您的输入之前冻结(除非您使用的是 Chrome,这清楚地表明这两者的实现方式完全不同) .

    即使这不是一个决定性的答案,我会说你最好手动做一些事情而不是 html5 原生的。它似乎没有被认为是结束它们的错误反馈。使用插件、框架或尝试一些 css 魔法like so

    【讨论】:

    • 哈哈,你把它黑到了核心。我对你的 setTimeout 有一个解决方案,尽管将方法 setTimeout 更改为 requestAnimationFrame 并删除 1000
    • requestAnimationFrame(() => { this.focus(); this.reportValidity(); $("#validateMe")[0].addEventListener("invalid", listener); });
    • @joyBlanks setTimeout 不存在不是因为我想等待一帧 - 这是因为他希望滚动动画平滑。这就是为什么现在将 setTimeout 设置为 500,因为我根据他的小提琴对其进行了微调。虽然我会注意 requestAnimatonFrame - 可能非常有用。
    • 当您在滚动完成后放入 requestAnimation 框架时,它将调用您的结果。例如,滚动完成
    • 谢谢阿图尔,这就像一个魅力。我还发现这个用于在滚动停止时进行监听:stackoverflow.com/questions/46795955/…
    【解决方案2】:

    似乎没有任何方法可以仅阻止默认行为的滚动,实际上,即使有,UA 的不同行为也可能使其成为不合适的选项:Chrome 确实会在您滚动时隐藏工具提示远离无效元素,因此不会显示,Firefox 使用此工具提示的固定位置,因此初始定位将关闭...

    所以我能想到的唯一解决方案确实是在平滑滚动完成后手动触发reportValidity

    不幸的是,当这种情况发生时没有内置事件会触发,因此我们需要提出一个手工解决方案。
    我们可以设置一个超时,但这并不可靠:
    对于执行此操作应该花费多长时间没有标准,例如in Chrome,该时间与所需的滚动量相关(即,它更多的是速度而不是持续时间),硬持续时间限制为 3s (oO)。

    如果reportValidity 的滚动发生在您的平滑完成之前,那么它将直接以behavior: "nearest" 模式跳转,从而破坏您的脚本:

    const input = document.querySelector('input');
    input.oninvalid = (evt) => {
      if( input._forced_invalidity ) {
        input._forced_invalidity = false;
        return;
      }
      evt.preventDefault();
      
      input.scrollIntoView({
        behavior: "smooth",
        block: "center"
      } )
      setTimeout(() => {
        input._forced_invalidity = true;
        input.reportValidity();  
      }, 200 );  // an hard coded value
    };
    p {
      height: 1000vh;
      width: 5px;
      background: repeat 0 0 / 5px 10px
        linear-gradient(to bottom, black 50%, white 50%);
    }
    <form id="myform" action="">
      <button>send</button>
      <p></p>
      <input type="text" required>
      <p></p>
      <button>send</button>
    </form>

    这里我将使用我制作的检测器,它会检查每一帧元素的位置是否发生了变化。只要它没有在两帧中发生,它就会解决一个 Promise。这仍然不是 100% 的防弹,但这应该比硬编码的超时更好。

    另一个复杂的情况是,似乎没有任何东西让我们知道被触发的事件是由对 reportValidity 的调用还是由表单的提交操作触发的。
    (实际上在Firefox,我们可以检查它们的非标准 explicitOriginalTarget 属性,但这仅适用于 FF...)
    所以我们需要在调用这个reportValidity 方法之前提出一个标志。 这变得很脏,但这是必需的......

    const input = document.querySelector('input');
    input.oninvalid = (evt) => {
      // that's our flag, see below
      if( input._forced_invalidity ) {
        input._forced_invalidity = false;
        return;
      }
      evt.preventDefault();
      
      smoothScroll( input, {
        block: "center"
      } )
      .then( () => {
        // raise our flag so we know we should not be blocked
        // and we should not fire a new scroll + reportValidity infinitely
        input._forced_invalidity = true;
        input.reportValidity();  
      } );  
    };
    
    
    // Promised based scrollIntoView( { behavior: 'smooth' } )
    function smoothScroll( elem, options ) {
      return new Promise( (resolve) => {
        if( !( elem instanceof Element ) ) {
          throw new TypeError( 'Argument 1 must be an Element' );
        }
        let same = 0;
        let lastPos = null;
        const scrollOptions = Object.assign( { behavior: 'smooth' }, options );
        elem.scrollIntoView( scrollOptions ); 
        requestAnimationFrame( check );
        
        function check() {
          const newPos = elem.getBoundingClientRect().top;
          if( newPos === lastPos ) {
            if(same ++ > 2) {
              return resolve();
            }
          }
          else {
            same = 0;
            lastPos = newPos;
          }
          requestAnimationFrame(check);
        }
      });
    }
    p {
      height: 400vh;
      width: 5px;
      background: repeat 0 0 / 5px 10px
        linear-gradient(to bottom, black 50%, white 50%);
    }
    <form id="myform" action="">
      <button>send</button>
      <p></p>
      <input type="text" required>
      <p></p>
      <button>send</button>
    </form>

    【讨论】:

    • 谢谢凯多。尽管我采用了 Artur 的解决方案,但您的解决方案看起来也可以。我会保存它以备将来尝试。
    • @gib65 请注意,尽管他们的超时解决方案远非可靠。至少在 Chrome 中,滚动时间不是由恒定的持续时间决定的,而是由速度决定的,即它与必须滚动的内容有关。硬编码的超时可能非常失败。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多