解决方法 - Safari不尊重Select≫的隐藏属性。选项元素

发布于 2025-02-04 11:29:36 字数 368 浏览 4 评论 0原文

我正在开发一种HTML形式,并意识到,iOS Safari在其他浏览器上(包括基于Android的浏览器),选择字段的行为大不相同。

Safari Mobile忽略了我的我的选项元素的属性,使用户可以选择占位符值。虽然可以设置禁用属性,该属性可以防止用户在iOS上选择它们,但仍将这些字段显示给用户,并且必须使用媒体查询来处理,因为该选项元素不是在其他浏览器中完全显示(即使是占位符)。

我想要一个无缝的通用解决方案。一些阅读和实验表明,Safari实际上正在剥离隐藏的属性,并从option s生成弹出声。

当然,如果不存在选项标签,它无法生成包含它们的弹出版...

I was developing an HTML form and realised that the behaviour of the select field was quite different on iOS Safari to other browsers (including several android based browsers).

Safari mobile was ignoring the hidden attribute of my option elements, allowing the user to select placeholder values. While it's possible to set the disabled attribute which prevents the user from selecting them on iOS, this still shows those fields to the user, and it has to be handled with media queries as then the option elements are not shown at all (even as placeholders) in other browsers.

I wanted a seamless universal solution. A bit of reading and experimentation indicated that Safari was actually stripping the hidden attribute and generating a popover from the options.

Of course, if the option tag isn't there, it can't generate a popover containing them...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

行雁书 2025-02-11 11:29:36

我的解决方案是将事件侦听器添加到:

  • touchstart& touchend暂时删除应该隐藏的选项(请注意,如果您在敲击时刷出元素,则似乎可以触发点击而无需触发触摸端)
  • 更改输入侦听器可防止代码运行多次运行,并且,
  • Blur focusout touchcancel侦听器使侦听器双重确保恢复该选项,如果用户取消选择选项。

此代码看起来相对较长,但是我已经写了以可读性的方式写了。它可能会受到重大压缩,并且性能命中率可以忽略不计。

我已经使用jQuery实现了它,但是我认为本机JS和QuerySelector()没有理由无法完成。

这种方法非常强大,可以被其他触摸输入(刷,滚动等)触发,并且能够在发生单击事件发生之前(似乎是在生成频率之后发生的) )。

它并不完美,但是它确实使Safari的行为与所有其他浏览器在这一小方面都充分融洽。

希望这对别人有帮助!

$(document).ready(function() {
  //Workaround - Safari/Ios - does not respect the <option hidden> attribute
  //Momentarily remove the hidden option element when the user clicks a select element.
  //Re-Add it after iOS has generated its selection popover

  var hiddenOption; //Holds the option element we want to be non-selectable extra option)
  var timers = []; //Collect setTimout timers

  function returnOptionToSelect(element) {
    if ($(element).children('[data-ioshidden]').length > 0) {
      //It's already there. Do nothing.
    } else {
      $(element).prepend(hiddenOption); //Put it back
    }
  }

  $('#jQueryWorkaroundForm select').on('touchstart touchend', function(e) {
    if ($(e.target).data('has-been-changed')) {
          //don't do anything
    } else {
      if($(e.target).children('[data-ioshidden]').length > 0){
        hiddenOption = $(e.target).children('[data-ioshidden]');
        $(hiddenOption).remove();
      }
      timers.push(setTimeout(returnOptionToSelect, 35, $(e.target))); //Nice short interval that's largely imperceptible, but long enough for the popover to generate
    }
  });

  $('#jQueryWorkaroundForm select').on('input', function(e) {

    if ($(e.target).data('has-been-changed')) {
      //do nothing
    } else {
      $(e.target).data('has-been-changed', true);
      if (navigator.maxTouchPoints && navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')){
        $(e.target).prop('selectedIndex', (e.target.selectedIndex+1)); //Need to bump the index +1 on ios
      }
      //make sure the option is gone
      for (var x in timers) {
        clearTimeout(x);
      }
      timers = [];
      if ($(e.target).children('[data-ioshidden]').length > 0) {
        $(e.target).children('[data-ioshidden]').remove();
      }
      hiddenOption = undefined; //throw away the option so it can't get put back
    }
  });

  $('#jQueryWorkaroundForm select').on('blur focusout touchcancel', function(e) {
    if ($(e.target).data('has-been-changed')) {
      //The user selected an option. Don't put the element back.
    } else {
      //The user did not select an option.
      if ($(e.target).children('[data-ioshidden]').length > 0) {
        //It's already there. Do nothing.
      } else {
        $(e.target).prepend(hiddenOption); //Put it back
      }
    }
  });

});
select{
width:300px;
}
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>

<body>
  <p>jQuery Workaround</p>
  <form id="jQueryWorkaroundForm">
    <select>
      <option hidden data-ioshidden value="">This option not selectable in any browser</option>
      <option>Option 1</option>
      <option>Option 2</option>
    </select>
  </form>
  <p>Standard HTML</p>
  <form id="standardHTMLForm">
    <select>
      <option value="" hidden>This option selectable in iOS Safari</option>
      <option>Option 1</option>
      <option>Option 2</option>
    </select>
  </form>
</body>

</html>

在这里,它是JS小提琴:

My solution was to add event listeners to:

  • touchstart & touchend to momentarily remove the option which should be hidden (note that it appears to be possible to trigger a tap without triggering touch end, if you swipe off the element while tapping)
  • change input listener prevents the code from running more than once, and,
  • blur focusout touchcancel listeners make doubly sure to reinstate the option if a user cancels selecting an option.

This code looks relatively long for what it is, but I've written it that way for readability. It could be significantly compressed, and the performance hit is negligible.

I have implemented it using jQuery, but I see no reason it couldn't be done with native JS and querySelector().

This approach is quite robust to being accidentally triggered by other touch input (swiping, scrolling etc.), and is able to respond long before the click event occurs (which seems to occur after the popover has been generated).

It's not perfect, but it does bring the behaviour of Safari well into line with all other browsers in this small aspect.

Hope this helps someone else!

$(document).ready(function() {
  //Workaround - Safari/Ios - does not respect the <option hidden> attribute
  //Momentarily remove the hidden option element when the user clicks a select element.
  //Re-Add it after iOS has generated its selection popover

  var hiddenOption; //Holds the option element we want to be non-selectable extra option)
  var timers = []; //Collect setTimout timers

  function returnOptionToSelect(element) {
    if ($(element).children('[data-ioshidden]').length > 0) {
      //It's already there. Do nothing.
    } else {
      $(element).prepend(hiddenOption); //Put it back
    }
  }

  $('#jQueryWorkaroundForm select').on('touchstart touchend', function(e) {
    if ($(e.target).data('has-been-changed')) {
          //don't do anything
    } else {
      if($(e.target).children('[data-ioshidden]').length > 0){
        hiddenOption = $(e.target).children('[data-ioshidden]');
        $(hiddenOption).remove();
      }
      timers.push(setTimeout(returnOptionToSelect, 35, $(e.target))); //Nice short interval that's largely imperceptible, but long enough for the popover to generate
    }
  });

  $('#jQueryWorkaroundForm select').on('input', function(e) {

    if ($(e.target).data('has-been-changed')) {
      //do nothing
    } else {
      $(e.target).data('has-been-changed', true);
      if (navigator.maxTouchPoints && navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')){
        $(e.target).prop('selectedIndex', (e.target.selectedIndex+1)); //Need to bump the index +1 on ios
      }
      //make sure the option is gone
      for (var x in timers) {
        clearTimeout(x);
      }
      timers = [];
      if ($(e.target).children('[data-ioshidden]').length > 0) {
        $(e.target).children('[data-ioshidden]').remove();
      }
      hiddenOption = undefined; //throw away the option so it can't get put back
    }
  });

  $('#jQueryWorkaroundForm select').on('blur focusout touchcancel', function(e) {
    if ($(e.target).data('has-been-changed')) {
      //The user selected an option. Don't put the element back.
    } else {
      //The user did not select an option.
      if ($(e.target).children('[data-ioshidden]').length > 0) {
        //It's already there. Do nothing.
      } else {
        $(e.target).prepend(hiddenOption); //Put it back
      }
    }
  });

});
select{
width:300px;
}
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>

<body>
  <p>jQuery Workaround</p>
  <form id="jQueryWorkaroundForm">
    <select>
      <option hidden data-ioshidden value="">This option not selectable in any browser</option>
      <option>Option 1</option>
      <option>Option 2</option>
    </select>
  </form>
  <p>Standard HTML</p>
  <form id="standardHTMLForm">
    <select>
      <option value="" hidden>This option selectable in iOS Safari</option>
      <option>Option 1</option>
      <option>Option 2</option>
    </select>
  </form>
</body>

</html>

And here it is as a JS fiddle: https://jsfiddle.net/wjcprbyg/97/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文