Czy można wymusić ignorowanie pseudoklasy: hover dla użytkowników iPhone'a / iPada?

95

W mojej witrynie mam kilka menu css, które rozwijają się o :hover(bez js)

Działa to w częściowo zepsuty sposób na iDevices, na przykład dotknięcie aktywuje :hoverregułę i rozszerzy menu, ale stuknięcie w innym miejscu nie spowoduje usunięcia :hover. Również jeśli w elemencie, który jest :hover'ed, znajduje się link , musisz dwukrotnie dotknąć, aby aktywować łącze (pierwsze :hoverdotknięcie wyzwala łącze, drugie dotknięcie wyzwala łącze).

Udało mi się sprawić, że wszystko działa dobrze na iPhonie, wiążąc touchstartwydarzenie.

Problem polega na tym, że czasami mobilne safari nadal wybiera wyzwalanie :hoverreguły z css zamiast z moich touchstartwydarzeń!

Wiem, że to jest problem, ponieważ kiedy wyłączam wszystkie :hoverreguły ręcznie w css, mobilne safari działa świetnie (ale zwykłe przeglądarki już oczywiście nie).

Czy istnieje sposób na dynamiczne „anulowanie” :hoverreguł dla niektórych elementów, gdy użytkownik korzysta z mobilnego safari?

Zobacz i porównaj zachowanie iOS tutaj: http://jsfiddle.net/74s35/3/ Uwaga: tylko niektóre właściwości css wyzwalają zachowanie dwóch kliknięć, np. Display: none; ale nie tło: czerwone; lub dekoracja tekstu: podkreślenie;

Christopher Camps
źródło
7
PROSZĘ NIE wyłączać najechania kursorem tylko z powodu obecności zdarzeń „dotyku”. Wpłynie to niekorzystnie na użytkowników laptopów wyposażonych w urządzenia dotykowe i myszy. Zobacz moją odpowiedź po więcej szczegółów
Simon_Weaver,

Odpowiedzi:

77

Zauważyłem, że „: hover” jest nieprzewidywalne w Safari na iPhonie / iPadzie. Czasami dotknięcie elementu powoduje, że element „: hover”, a czasami dryfuje do innych elementów.

Na razie mam po prostu lekcje bez dotykania ciała.

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

I wszystkie reguły CSS mają z „: hover” poniżej „.no-touch”:

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

Gdzieś na stronie mam javascript do usunięcia klasy bezdotykowej z ciała.

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

To nie wygląda idealnie, ale i tak działa.

Morgan Cheng
źródło
5
To jedyny sposób, aby to zrobić. Martwi mnie to, że zanieczyszcza „normalną” witrynę treściami, które tam nie pasują i są nieistotne. No cóż. Dzięki.
Christopher Camps
Możesz również użyć zapytań o media z rezerwą dla IE
Lime
2
@Shackrock: Nie sądzę, aby podejście „jedno dotknięcie, aby najechać i dwa, aby kliknąć” jest dobrym rozwiązaniem. Ponieważ w rzeczywistości na urządzeniu dotykowym nie ma faktycznego najeżdżania (dopóki nie są wyposażone w ekrany, które wyczuwają, że znajduje się nad nim palec), myślę, że lepiej w ogóle nie symulować zawisu. W przypadku efektów, takich jak te typowe dla przycisków i łączy, po prostu pomiń efekt. W przypadku menu, które rozwijają się po najechaniu kursorem, rozszerz je po dotknięciu / kliknięciu. Mój 2c.
Adrian Schmidt
18
Ostatnio zauważyłem problemy w tego rodzaju podejściu z powodu nowych systemów Windows 8 z obsługą dotykową i myszą
Tom,
4
+1 do komentarza Toma - sprawdzanie startu ontouch powróci do wartości true na laptopach obsługujących dotyk, nawet po podłączeniu do zewnętrznego monitora (gdzie bardziej pożądana jest strona przyjazna dla myszy ze stanami najechania kursorem).
gregdev
45

:hoverto nie jest problem. Safari na iOS działa według bardzo dziwnej zasady. Strzela mouseoveri mousemovepierwszy; jeśli cokolwiek zostanie zmienione podczas tych wydarzeń, „kliknięcie” i powiązane zdarzenia nie zostaną uruchomione:

Schemat zdarzenia dotykowego w iOS

mouseenteri mouseleavewydają się być uwzględnione, chociaż nie są określone w wykresie.

Jeśli w wyniku tych zdarzeń zmienisz cokolwiek, zdarzenia kliknięcia nie zostaną uruchomione. Obejmuje to coś wyżej w drzewie DOM. Na przykład zapobiegnie to działaniu pojedynczych kliknięć w Twojej witrynie za pomocą jQuery:

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

Edycja: Dla wyjaśnienia, hoverzdarzenie jQuery zawiera mouseenteri mouseleave. Oba zapobiegną clickzmianie treści.

Zenexer
źródło
5
oczywiście: problemem jest hover :-), a raczej błędna implementacja Safari. to interesujące tło problemu, ale Apple jest jedyną stroną, która może naprawić to straszne zachowanie. musi albo „odsłonić”, gdy kliknie się coś poza elementami, albo nigdy nie „najechać kursorem”. obecne zachowanie w zasadzie psuje każdą stronę internetową, która używa: hover for a mouse
Simon_Weaver
to pomogło mi zdać sobie sprawę, że moduł hoverobsługi jquery uniemożliwiał clicktrafienie oddzielnego modułu obsługi - co za żart!
Simon_Weaver,
1
Genialna odpowiedź i najlepsze wyjaśnienie problemu, z jakim się spotkałem. Nie tylko ruchy CSS powodują problem z podwójnym kliknięciem, ale dosłownie każda modyfikacja DOM po zdarzeniach mouseover / mouseenter et al.
Oliver Joseph Ash
Oto przykład, który pokazuje, że zdarzenie kliknięcia nie uruchamia się, gdy DOM jest modyfikowany na mouseenter jsbin.com/maseti/5/edit?html,js,output
Oliver Joseph Ash
@Oliver Joseph Ash Right, stąd przykład JS w odpowiedzi. ;)
Zenexer
18

Modernizer biblioteki wykrywania funkcji przeglądarki obejmuje sprawdzanie zdarzeń dotykowych.

Domyślnym zachowaniem jest zastosowanie klas do elementu HTML dla każdej wykrytej funkcji. Następnie możesz użyć tych klas do stylizacji dokumentu.

Jeśli zdarzenia dotykowe nie są włączone, Modernizr może dodać klasę no-touch:

<html class="no-touch">

Następnie określ zakres stylów najechania na tę klasę:

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

Możesz pobrać niestandardową kompilację Modernizr, aby uwzględnić tak mało lub tyle funkcji wykrywania funkcji, ile potrzebujesz.

Oto przykład niektórych klas, które można zastosować:

<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">
Beau Smith
źródło
Należy pamiętać, że w najnowszych wersjach Chrome na komputerach stacjonarnych Modernizr zgłasza „dotknięcie”. Jest to najwyraźniej poprawione w wersji 3 za pomocą touchevents.
Craig Jacobs
18

Niektóre urządzenia (jak powiedzieli inni) obsługują zarówno zdarzenia dotyku, jak i myszy. Na przykład Microsoft Surface ma ekran dotykowy, gładzik ORAZ rysik, który faktycznie podnosi zdarzenia najechania kursorem, gdy znajduje się nad ekranem.

Każde rozwiązanie, które wyłącza się :hoverna podstawie obecności zdarzeń „dotyku”, będzie miało również wpływ na użytkowników Surface (i wiele innych podobnych urządzeń). Wiele nowych laptopów jest dotykowych i reaguje na zdarzenia dotykowe - więc wyłączenie najechania kursorem jest naprawdę złą praktyką.

To błąd w Safari, nie ma absolutnie żadnego uzasadnienia dla tego okropnego zachowania. Odmawiam sabotowania przeglądarek innych niż iOS z powodu błędu w iOS Safari, który najwyraźniej istnieje od lat. Naprawdę mam nadzieję, że naprawią to w iOS8 w przyszłym tygodniu, ale w międzyczasie ....

Moje rozwiązanie :

Niektórzy już sugerowali użycie Modernizra, no cóż, Modernizr pozwala na tworzenie własnych testów. To, co tutaj robię, to „abstrahowanie” idei przeglądarki, która obsługuje, :hoverdo testu Modernizr, którego mogę używać w całym kodzie bez kodowania 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;
 });

Wtedy css staje się czymś takim

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

Tylko w systemie iOS ten test zakończy się niepowodzeniem i wyłączy najazd.

Najlepszą częścią takiej abstrakcji jest to, że jeśli stwierdzę, że psuje się na pewnym Androidzie lub jeśli jest naprawiony w iOS9, mogę po prostu zmodyfikować test.

Simon_Weaver
źródło
To rozwiązanie jest zarówno paskudne, jak i najlepsze na rynku. Zepsuje się tylko wtedy, gdy przeglądarka, która ma problemy z najechaniem kursorem, obsługuje zarówno dotyk, jak i kliknięcie, ale w przypadku przeglądarek obsługujących tylko dotyk na iPadzie / iPhonie tak nie jest. Moja sugestia jest taka, aby zaimplementować to tylko jako poprawkę optymalizacyjną (aby usunąć migotanie kursora, gdy już używasz fastclick.js) i upewnić się, że wszystko działa również bez niego.
Gersom,
Dzięki. Zgadzam się. Ciekawe, co robi iOS 9 w przypadku tego problemu - jeśli cokolwiek.
Simon_Weaver
Poszedłem w przeciwnym kierunku:html:not(.no-hover) .category:hover { ...
Chris Marisic
+1 dla "Safari nie ogłasza" światu, że zachowuje się źle, używając: hover "Ten problem sprawia, że ​​moim zdaniem iOS znacznie gorszy niż jakakolwiek wersja IE ...
Spencer O'Reilly
17

Lepsze rozwiązanie, bez żadnej klasy JS, css i sprawdzania widoku: możesz korzystać z funkcji interakcji z mediami (zapytania o media poziom 4)

Lubię to:

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

Obsługuje to iOS Safari

Więcej informacji: https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/

Estevão Lucas
źródło
Jednak nie obsługiwane przez Firefoksa (oczywiście w czasie tego komentarza)
Frank,
Jest to teraz obsługiwane również przez przeglądarkę Firefox (FF 64+, wydana w grudniu 2018 r.).
uczcz
16

Dodanie biblioteki FastClick do strony spowoduje, że wszystkie dotknięcia urządzenia mobilnego zostaną zamienione w zdarzenia kliknięcia (niezależnie od tego, gdzie użytkownik kliknie), więc powinno również rozwiązać problem z najechaniem kursorem na urządzeniach mobilnych. Edytowałem twoje skrzypce jako przykład: http://jsfiddle.net/FvACN/8/ .

Po prostu umieść bibliotekę fastclick.min.js na swojej stronie i aktywuj przez:

FastClick.attach(document.body);

Dodatkową korzyścią jest również usunięcie irytującego opóźnienia onClick 300 ms, na które cierpią urządzenia mobilne.


Istnieje kilka drobnych konsekwencji korzystania z FastClick, które mogą, ale nie muszą mieć znaczenia dla Twojej witryny:

  1. Jeśli dotkniesz gdzieś na stronie, przewiniesz w górę, przewiniesz z powrotem w dół, a następnie puścisz palec dokładnie w tym samym miejscu, w którym go początkowo umieściłeś, FastClick zinterpretuje to jako „kliknięcie”, chociaż oczywiście nie jest. Przynajmniej tak to działa w wersji FastClick, której obecnie używam (1.0.0). Ktoś mógł rozwiązać problem od czasu tej wersji.
  2. FastClick usuwa możliwość „dwukrotnego kliknięcia” przez kogoś.
Troy
źródło
1
Czy zechciałbyś podzielić się innym konkretnym przykładem, jak mogę to zrobić dla mojej witryny internetowej. Nie wiem nic o javascript, więc jestem trochę zdezorientowany, jak to zadziała. Mam wszystkie te linki w mojej witrynie, na które użytkownicy zwykle najeżdżają kursorem przed kliknięciem, ale w przypadku iPada / telefonu komórkowego chcę, aby nie musieli klikać dwa razy.
Andy
@Andy - Nie jest do końca jasne, czego potrzebujesz, a czego brakuje ... Czego próbowałeś do tej pory i jakie błędy widzisz? Może najlepiej byłoby utworzyć nowe pytanie Stackoverflow, jeśli jeszcze tego nie zrobiłeś, z przykładem tego, co próbujesz zrobić (?) Lub spróbuj wyjaśnić, co to jest w powyższym przykładzie jsfiddle, którego nie robisz nie rozumiem.
Troy
czy można tego używać w aplikacjach jednostronicowych? Mam na myśli, kiedy zawartość DOM jest aktualizowana za każdym razem
Sebastien Lorber
Tak, używam go z aplikacjami jednostronicowymi.
Troy,
4

Zasadniczo istnieją trzy scenariusze:

  1. Użytkownik ma tylko mysz / urządzenie wskazujące i może aktywować:hover
  2. Użytkownik ma tylko ekran dotykowy i nie może aktywować :hoverelementów
  3. Użytkownik ma zarówno ekran dotykowy, jak i urządzenie wskazujące

Pierwotnie przyjęty odpowiedź działa świetnie, jeśli to możliwe są tylko dwa pierwsze scenariusze, gdzie użytkownik ma zarówno wskaźnik lub ekranu dotykowego. Było to powszechne, gdy PO zadał to pytanie 4 lata temu. Kilku użytkowników wskazało, że urządzenia z systemem Windows 8 i Surface zwiększają prawdopodobieństwo trzeciego scenariusza.

Rozwiązanie iOS problemu braku możliwości najechania kursorem na urządzenia z ekranem dotykowym (zgodnie z opisem @Zenexer) jest sprytne, ale może spowodować niewłaściwe zachowanie prostego kodu (jak zauważył OP). Wyłączenie wskaźnika tylko dla urządzeń z ekranem dotykowym oznacza, że ​​nadal będziesz musiał kodować alternatywę przyjazną dla ekranu dotykowego. Wykrywanie, kiedy użytkownik ma zarówno wskaźnik, jak i ekran dotykowy, dodatkowo zagmatwa wody (zgodnie z wyjaśnieniem @Simon_Weaver).

W tym momencie najbezpieczniejszym rozwiązaniem jest unikanie używania :hoverjako jedynego sposobu interakcji użytkownika z Twoją witryną. Efekty najechania kursorem to dobry sposób na wskazanie, że link lub przycisk jest aktywny, ale użytkownik nie powinien być zmuszony do najechania kursorem na element, aby wykonać akcję w Twojej witrynie.

Ponowne przemyślenie funkcji „najechania kursorem” z myślą o ekranach dotykowych stanowi dobrą dyskusję na temat alternatywnych podejść do UX. Rozwiązania podane w odpowiedzi to:

  • Zastąpienie menu podręcznego akcjami bezpośrednimi (zawsze widoczne linki)
  • Zastępowanie menu wyświetlanych po najechaniu kursorem na menu podręczne
  • Przenoszenie dużej ilości treści wyświetlanych po najechaniu na osobną stronę

Idąc dalej, będzie to prawdopodobnie najlepsze rozwiązanie dla wszystkich nowych projektów. Przyjęta odpowiedź jest prawdopodobnie drugim najlepszym rozwiązaniem, ale należy wziąć pod uwagę urządzenia, które również mają urządzenia wskazujące. Uważaj, aby nie wyeliminować funkcji, gdy urządzenie ma ekran dotykowy tylko po to, aby obejść :hoverhack iOS .

trzecia osoba
źródło
1

Wersja JQuery w twoim .css używa .no-touch .my-element: hover dla wszystkich reguł hover obejmuje JQuery i następujący skrypt

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

Następnie w tagu body dodaj class = "no-touch" ontouchstart = "removeHoverState ()"

gdy tylko ontouchstart odpali, klasa dla wszystkich stanów najechania jest usuwana

Greg
źródło
0

Zamiast mieć efekty najechania tylko wtedy, gdy dotyk nie jest dostępny, stworzyłem system do obsługi zdarzeń dotykowych i to rozwiązało problem za mnie. Najpierw zdefiniowałem obiekt do testowania pod kątem zdarzeń „tap” (odpowiednik „kliknięcia”).

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;
      }
   }
};

Następnie w moim module obsługi zdarzeń wykonuję coś takiego:

$('#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.
            });
pączek
źródło
0

Dzięki @Morgan Cheng za odpowiedź, jednak nieznacznie zmodyfikowałem funkcję JS, aby uzyskać " touchstart " (kod wzięty z odpowiedzi @Timothy Perez ), ale potrzebujesz do tego jQuery 1.7+

  $(document).on({ 'touchstart' : function(){
      //do whatever you want here
    } });
3Gee
źródło
0

Biorąc pod uwagę odpowiedź dostarczoną przez Zenexer, wzorzec, który nie wymaga dodatkowych tagów HTML, to:

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
    }
});

Ta metoda uruchamia się najpierw po najechaniu kursorem myszy, a następnie po kliknięciu.

bardzofatyczny
źródło
0

Zgadzam się, że najlepszym rozwiązaniem jest wyłączenie wskaźnika na dotyk.

Jednak, aby zaoszczędzić sobie kłopotu z ponownym pisaniem swojego css, po prostu zawiń wszystkie :hoverelementy @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)" />

Jordan Young
źródło
0

Dla osób z typowym przypadkiem wyłączania :hoverzdarzeń w iOS Safari najprostszym sposobem jest użycie zapytania o media o minimalnej szerokości dla :hoverwydarzeń, które pozostaje powyżej szerokości ekranu urządzeń, których unikasz. Przykład:

@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;
  }
}
Brian Scramlin
źródło
-6

Spójrz tylko na rozmiar ekranu ...

@media (min-width: 550px) {
    .menu ul li:hover > ul {
    display: block;
}
}
oeliewoep
źródło
-12

oto kod, w którym chcesz go umieścić

// 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
}
Łukasz
źródło