Можно ли принудительно игнорировать псевдокласс: hover для пользователей iPhone / iPad?

94

У меня есть несколько меню css на моем сайте, которые расширяются с помощью :hover(без js)

Это работает частично на iDevices, например, касание активирует :hoverправило и расширяет меню, но затем касание в другом месте не удаляет :hover. Также, если есть ссылка внутри элемента, который является :hover'ed, вам нужно дважды нажать, чтобы активировать ссылку (первое :hoverнажатие запускает ссылку, второе нажатие запускает ссылку).

Я смог заставить все работать на iphone, привязав touchstartсобытие.

Проблема в том, что иногда мобильное сафари по-прежнему выбирает запуск :hoverправила из CSS, а не из моих touchstartсобытий!

Я знаю, что это проблема, потому что, когда я :hoverвручную отключаю все правила в CSS, мобильное сафари работает отлично (но обычные браузеры, очевидно, больше не работают).

Есть ли способ динамически «отменять» :hoverправила для определенных элементов, когда пользователь находится в мобильном сафари?

Посмотрите и сравните поведение iOS здесь: http://jsfiddle.net/74s35/3/ Примечание: только некоторые свойства css вызывают поведение при двух щелчках, например display: none; но не фон: красный; или текст-украшение: подчеркивание;

Кристофер Кэмпс
источник
7
ПОЖАЛУЙСТА, НЕ отключайте наведение только из-за наличия событий касания. Это отрицательно скажется на пользователях ноутбуков с сенсорными устройствами ввода и мышью. Подробнее см. Мой ответ
Simon_Weaver

Ответы:

77

Я обнаружил, что ": hover" непредсказуемо в iPhone / iPad Safari. Иногда нажатие на элемент делает этот элемент «: hover», а иногда он перемещается к другим элементам.

В настоящее время у меня просто класс «без прикосновения» к телу.

<body class="yui3-skin-sam no-touch">
   ...
</body>

И иметь все правила CSS с ": hover" ниже ".no-touch":

.no-touch my:hover{
   color: red;
}

Где-то на странице у меня есть javascript для удаления класса без касания из тела.

if ('ontouchstart' in document) {
    Y.one('body').removeClass('no-touch');
}

Это не выглядит идеально, но все равно работает.

Морган Ченг
источник
5
Это единственный способ сделать это. Что меня беспокоит, так это то, что он загрязняет «нормальный» сайт вещами, которые ему не подходят и не имеют отношения к делу. Ну что ж. Спасибо.
Кристофер Кэмпс,
Вы также можете использовать медиа-запросы с резервным вариантом для IE
Lime
2
@Shackrock: Я не думаю, что подход «одно нажатие для наведения и два для щелчка» - хорошее решение. Поскольку на самом деле на сенсорном устройстве нет фактического наведения (до тех пор, пока они не оснащены экранами, которые чувствуют, что ваш палец наведен на него), я думаю, что лучше вообще не имитировать наведение. Для эффектов, подобных тем, которые используются для кнопок и ссылок, просто пропустите эффект. Для меню, которые расширяются при наведении курсора, вместо этого заставляйте их расширяться при нажатии / щелчке. Мой 2c.
Адриан Шмидт
18
Недавно я обнаружил проблемы в подобном подходе из-за новых систем Windows 8 с сенсорным вводом и вводом мыши
Том
4
+1 к комментарию Тома - проверка ontouchstart вернет true на ноутбуках с сенсорным экраном, даже если они подключены к внешнему монитору (где более желателен сайт, удобный для мыши с состояниями наведения).
gregdev
45

:hoverздесь не проблема. Safari для iOS следует очень странному правилу. Он срабатывает mouseoverи mousemoveпервым; если что-то изменится во время этих событий, событие click и связанные с ним события не будут запущены:

Схема сенсорного события в iOS

mouseenter и mouseleave кажутся включенными, хотя в таблице они не указаны.

Если вы измените что-либо в результате этих событий, события щелчка не будут запускаться. Это включает что-то более высокое в дереве DOM. Например, это предотвратит работу вашего веб-сайта с помощью jQuery с помощью одиночных кликов:

$(window).on('mousemove', function() {
    $('body').attr('rel', Math.random());
});

Изменить: для пояснения hoverсобытие jQuery включает в себя mouseenterи mouseleave. Они оба предотвратить , clickесли содержание изменяется.

Зенексер
источник
5
конечно: проблема заключается в зависании :-) или, скорее, в его глючной реализации в Safari. это интересная история вопроса, но Apple - единственная сторона, которая может исправить это ужасное поведение. он либо должен «снимать наведение» при нажатии на что-то вне элементов, либо вообще никогда не «зависать». текущее поведение в основном нарушает работу любого веб-сайта, который использует: наведение для мыши
Simon_Weaver
это помогло мне понять, что hoverобработчик jquery предотвращает попадание отдельного clickобработчика - что за шутка!
Simon_Weaver
1
Блестящий ответ и лучшее объяснение проблемы, с которой я столкнулся. Проблема "двойного щелчка" возникает не только при наведении курсора CSS, но и буквально при любом изменении DOM после событий mouseover / mouseenter и др.
Оливер Джозеф Эш
Вот пример, который показывает, что событие click не срабатывает, когда DOM изменен на mouseenter jsbin.com/maseti/5/edit?html,js,output
Оливер Джозеф Эш
@Oliver Joseph Ash Правильно, отсюда и пример JS в ответе. ;)
Zenexer
18

Библиотека обнаружения функций браузера Modernizer включает проверку событий касания.

По умолчанию он применяет классы к вашему элементу html для каждой обнаруженной функции. Затем вы можете использовать эти классы для стилизации вашего документа.

Если сенсорные события не включены, Modernizr может добавить класс no-touch:

<html class="no-touch">

А затем используйте этот класс для ваших стилей наведения:

.no-touch a:hover { /* hover styles here */ }

Вы можете скачать кастомную сборку Modernizr будет включать в себя как можно меньше или столько функций, сколько вам нужно.

Вот пример некоторых классов, которые можно применить:

<html class="js no-touch postmessage history multiplebgs
             boxshadow opacity cssanimations csscolumns cssgradients
             csstransforms csstransitions fontface localstorage sessionstorage
             svg inlinesvg no-blobbuilder blob bloburls download formdata">
Бо Смит
источник
Имейте в виду, что в последних версиях Chrome на настольных компьютерах, которые Modernizr сообщает "касание". Это, по-видимому, исправлено в версии 3 с помощью touchevents.
Крейг Джейкобс,
18

Некоторые устройства (как говорили другие) имеют события касания и мыши. Microsoft Surface, например, имеет сенсорный экран, трекпад И стилус, который фактически вызывает события наведения при наведении курсора на экран.

Любое решение, которое отключается :hoverиз-за наличия событий касания, также повлияет на пользователей Surface (и многие другие подобные устройства). Многие новые ноутбуки сенсорные и будут реагировать на сенсорные события, поэтому отключение зависания - действительно плохая практика.

Это ошибка Safari, такому ужасному поведению нет абсолютно никаких оправданий. Я отказываюсь саботировать браузеры, отличные от iOS, из-за ошибки в iOS Safari, которая, по-видимому, существует уже много лет. Я очень надеюсь, что они исправят это для iOS8 на следующей неделе, но пока ....

Мое решение :

Некоторые уже предлагали использовать Modernizr, ну, Modernizr позволяет создавать свои собственные тесты. По сути, я здесь «абстрагирую» идею браузера, который поддерживает, :hoverв тест Modernizr, который я могу использовать в своем коде без жесткого кодирования if (iOS).

 Modernizr.addTest('workinghover', function ()
 {
      // Safari doesn't 'announce' to the world that it behaves badly with :hover
      // so we have to check the userAgent  
      return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? false : true;
 });

Тогда css становится примерно таким

html.workinghover .rollover:hover 
{
    // rollover css
}

Только на iOS этот тест завершится ошибкой и отключит опрокидывание.

Лучшая часть такой абстракции заключается в том, что если я обнаружу, что она ломается на определенном Android или если она исправлена ​​в iOS9, я могу просто изменить тест.

Simon_Weaver
источник
Это решение одновременно отвратительное и лучшее. Он ломается только в том случае, если в браузере, который борется с наведением курсора, есть и касание, и щелчок, но для браузеров с сенсорным управлением на ipad / iphone это не так. Я предлагаю реализовать это только как исправление оптимизации (чтобы убрать мерцание при наведении курсора, когда уже используется fastclick.js) и убедиться, что все работает и без него.
Gersom
Спасибо. Я согласен. Любопытно узнать, что делает iOS 9 с этой проблемой - если что.
Simon_Weaver
Я пошел в противоположном направлении:html:not(.no-hover) .category:hover { ...
Крис Марисич 08
+1 за «Safari не« объявляет »миру, что он плохо себя ведет с: hover» По моему мнению, эта проблема делает iOS намного хуже, чем любая версия IE ...
Спенсер О'Рейли
16

Добавление библиотеки FastClick на вашу страницу приведет к тому, что все нажатия на мобильном устройстве будут преобразованы в события щелчка (независимо от того, где пользователь нажимает), поэтому это также должно решить проблему зависания на мобильных устройствах. Я отредактировал вашу скрипку в качестве примера: http://jsfiddle.net/FvACN/8/ .

Просто включите библиотеку fastclick.min.js на свою страницу и активируйте ее через:

FastClick.attach(document.body);

В качестве дополнительного преимущества это также устранит раздражающую задержку onClick в 300 мс, от которой страдают мобильные устройства.


Есть несколько незначительных последствий использования FastClick, которые могут иметь или не иметь значения для вашего сайта:

  1. Если вы нажмете где-нибудь на странице, прокрутите вверх, прокрутите назад вниз, а затем отпустите палец в том же месте, где вы его изначально поместили, FastClick интерпретирует это как «щелчок», хотя это явно не так. По крайней мере, так это работает в версии FastClick, которую я сейчас использую (1.0.0). Кто-то, возможно, исправил проблему с этой версии.
  2. FastClick убирает возможность "двойного щелчка".
Трой
источник
1
Не могли бы вы поделиться еще одним конкретным примером того, как я могу сделать это для своего веб-сайта. Я ничего не знаю о javascript, поэтому немного не понимаю, как это будет работать. У меня есть все эти ссылки на моем веб-сайте, на которые пользователи обычно наводят курсор перед тем, как нажимать, но для ipad / mobile я хочу сделать так, чтобы им не приходилось нажимать дважды.
Энди
@Andy - Не совсем понятно, что вам нужно и чего не хватает ... Что вы пробовали до сих пор и какие ошибки вы видите, если они есть? Может быть, было бы лучше создать новый вопрос Stackoverflow, если вы еще этого не сделали, с примером того, что вы пытаетесь сделать (?) Или попытаться уточнить, что в приведенном выше примере jsfiddle, что вы не делаете Т понять.
Трой
можно ли это использовать в одностраничных приложениях? Я имею в виду, когда контент DOM обновляется каждый раз,
Себастьен Лорбер,
Да, использую с одностраничными приложениями.
Трой
16

Лучшее решение, без каких-либо проверок JS, классов css и области просмотра: вы можете использовать функции взаимодействия с мультимедиа (уровень медиа-запросов 4)

Как это:

@media (hover) {
  // properties
  my:hover {
    color: red;
  }
}

iOS Safari поддерживает это

Подробнее о: https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

Эстеван Лукас
источник
Хотя не поддерживается Firefox (конечно, на момент написания этого комментария)
Фрэнк
Теперь это также поддерживается Firefox (FF 64+, выпущен в декабре 2018 г.).
wunch
4

В основном есть три сценария:

  1. У пользователя есть только мышь / указатель, и он может активировать:hover
  2. У пользователя только сенсорный экран, и он не может активировать :hoverэлементы
  3. У пользователя есть как сенсорный экран, так и указатель.

Первоначально принят ответ отлично работает , если только первые два возможных сценария, когда пользователь имеет либо указатель или сенсорный экран. Это было обычным явлением, когда ОП задал вопрос 4 года назад. Несколько пользователей отметили, что устройства с Windows 8 и Surface делают третий сценарий более вероятным.

Решение iOS для проблемы невозможности зависания на устройствах с сенсорным экраном (как описано в @Zenexer) является умным, но может привести к неправильному поведению простого кода (как отмечено OP). Отключение наведения только для устройств с сенсорным экраном означает, что вам все равно нужно будет написать альтернативу для сенсорного экрана. Обнаружение того, что у пользователя есть и указатель, и сенсорный экран, еще больше запутывает воду (как объяснил @Simon_Weaver).

На данный момент самое безопасное решение - избегать использования :hoverкак единственного способа взаимодействия пользователя с вашим сайтом. Эффекты наведения - хороший способ указать, что ссылка или кнопка активны, но от пользователя не требуется навести курсор на элемент для выполнения действия на вашем веб-сайте.

Переосмысление функциональности «наведения» с учетом сенсорных экранов дает хорошее обсуждение альтернативных подходов к UX. Решения, представленные в ответе, включают:

  • Замена всплывающих меню прямыми действиями (всегда видимые ссылки)
  • Замена меню при наведении на контекстное меню
  • Перемещение большого количества контента при наведении на отдельную страницу

В дальнейшем это, вероятно, будет лучшим решением для всех новых проектов. Принятый ответ, вероятно, является вторым лучшим решением, но обязательно учтите устройства, которые также имеют указательные устройства. Будьте осторожны, чтобы не исключить функциональность, когда на устройстве есть сенсорный экран, чтобы обойти :hoverвзлом iOS .

садовод
источник
1

Версия JQuery в вашем .css использует .no-touch .my-element: hover для всех ваших правил наведения, включая JQuery и следующий скрипт

function removeHoverState(){
    $("body").removeClass("no-touch");
}

Затем в теге body добавьте class = "no-touch" ontouchstart = "removeHoverState ()"

как только ontouchstart запускает класс для всех состояний наведения, удаляется

Грег
источник
0

Вместо того, чтобы иметь только эффекты наведения, когда касание недоступно, я создал систему для обработки сенсорных событий, и это решило для меня проблему. Сначала я определил объект для тестирования событий «касания» (эквивалент «щелчка»).

touchTester = 
{
    touchStarted: false
   ,moveLimit:    5
   ,moveCount:    null
   ,isSupported:  'ontouchend' in document

   ,isTap: function(event)
   {
      if (!this.isSupported) {
         return true;
      }

      switch (event.originalEvent.type) {
         case 'touchstart':
            this.touchStarted = true;
            this.moveCount    = 0;
            return false;
         case 'touchmove':
            this.moveCount++;
            this.touchStarted = (this.moveCount <= this.moveLimit);
            return false;
         case 'touchend':
            var isTap         = this.touchStarted;
            this.touchStarted = false;
            return isTap;
         default:
            return true;
      }
   }
};

Затем в моем обработчике событий я делаю что-то вроде следующего:

$('#nav').on('click touchstart touchmove touchend', 'ul > li > a'
            ,function handleClick(event) {
               if (!touchTester.isTap(event)) {
                  return true;
               }

               // touch was click or touch equivalent
               // nromal handling goes here.
            });
пончик
источник
0

Спасибо @Morgan Cheng за ответ, однако я немного изменил функцию JS для получения " touchstart " (код взят из ответа @Timothy Perez ), однако для этого вам понадобится jQuery 1.7+

  $(document).on({ 'touchstart' : function(){
      //do whatever you want here
    } });
3Gee
источник
0

Учитывая ответ, предоставленный Zenexer, шаблон, не требующий дополнительных тегов HTML, выглядит следующим образом:

jQuery('a').on('mouseover', function(event) {
    event.preventDefault();
    // Show and hide your drop down nav or other elem
});
jQuery('a').on('click', function(event) {
    if (jQuery(event.target).children('.dropdown').is(':visible') {
        // Hide your dropdown nav here to unstick
    }
});

Этот метод сначала запускает наведение указателя мыши, а затем щелчок.

оченьфатический
источник
0

Я согласен, что отключение наведения для касания - лучший вариант.

Однако, чтобы избавить себя от необходимости переписывать CSS, просто оберните любые :hoverэлементы в @supports not (-webkit-overflow-scrolling: touch) {}

.hover, .hover-iOS {
  display:inline-block;
  font-family:arial;
  background:red;
  color:white;
  padding:5px;
}
.hover:hover {
  cursor:pointer;
  background:green;
}

.hover-iOS {
  background:grey;
}

@supports not (-webkit-overflow-scrolling: touch) {
  .hover-iOS:hover {
    cursor:pointer;
    background:blue;
  }

}
<input type="text" class="hover" placeholder="Hover over me" />

<input type="text" class="hover-iOS" placeholder="Hover over me (iOS)" />

Джордан Янг
источник
0

Для тех, кто часто использует отключение :hoverсобытий в iOS Safari, самый простой способ - использовать медиа-запрос минимальной ширины для ваших :hoverсобытий, который остается выше ширины экрана устройств, которых вы избегаете. Пример:

@media only screen and (min-width: 1024px) {
  .my-div:hover { // will only work on devices larger than iOS touch-enabled devices. Will still work on touch-enabled PCs etc.
    background-color: red;
  }
}
Брайан Скремлин
источник
-6

Вы только посмотрите на размер экрана ....

@media (min-width: 550px) {
    .menu ul li:hover > ul {
    display: block;
}
}
Oeliewoep
источник
-12

вот код, в который вы хотите его поместить

// a function to parse the user agent string; useful for 
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
    var pattern = new RegExp(vs, 'i');
    return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
    // iPad specific stuff here
}
Люк
источник