Как предотвратить эффекты липкого наведения для кнопок на сенсорных устройствах

Я создал карусель с предыдущей и следующей кнопки, которые видны всегда. Эти кнопки имеют состояние наведения, они становятся синими. На сенсорных устройствах, таких как iPad, состояние наведения липкое, поэтому кнопка остается синей после нажатия на нее. Я не хочу этого.

  • я мог бы добавить no-hover класс ontouchend для каждой кнопки, и сделать мой CSS выглядит так:button:not(.no-hover):hover { background-color: blue; } но это, вероятно, довольно плохо для производительности и не делает обрабатывать устройства, такие как Chromebook Pixel (который имеет сенсорный экран и мышь) правильно.

  • я мог бы добавить touch класс documentElement и сделать мой CSS вот так:html:not(.touch) button:hover { background-color: blue; } но это также не работает на устройствах как с сенсорным и мышь.

то, что я бы предпочел, это удаление состояния наведения ontouchend. Но не похоже, что это возможно. Фокусировка другого элемента не удаляет состояние наведения. Нажатие другого элемента вручную делает, но я не могу вызвать что в JavaScript.

все решения, которые я нашел, кажется несовершенным. Есть ли идеальное решение?

19 ответов


вы можете удалить состояние наведения, временно удалив ссылку из DOM. См.http://testbug.handcraft.com/ipad.html


в CSS у вас есть:

:hover {background:red;}

в JS у вас есть:

function fix()
{
    var el = this;
    var par = el.parentNode;
    var next = el.nextSibling;
    par.removeChild(el);
    setTimeout(function() {par.insertBefore(el, next);}, 0)
}

и затем в вашем HTML у вас есть:

<a href="#" ontouchend="this.onclick=fix">test</a>

один раз CSS медиа запросы Уровень 4 реализовано, вы сможете это сделать:

@media (hover: hover) {
    button:hover {
        background-color: blue;
    }
}

или на английском языке: "если браузер поддерживает правильное/истинное/реальное / неэмулированное наведение (например, имеет первичное устройство ввода, подобное мыши), примените этот стиль, когда buttons завис над."

поскольку эта часть медиа-запросов уровня 4 до сих пор была реализована только в bleeding-edge Chrome,я написал polyfill чтобы справиться с этим. Используя его, вы можете преобразуйте выше футуристический CSS в:

html.my-true-hover button:hover {
    background-color: blue;
}

(вариант .no-touch техника), а затем с помощью некоторого клиентского JavaScript из того же polyfill, который обнаруживает поддержку зависания, вы можете переключить наличие my-true-hover класса соответственно:

$(document).on('mq4hsChange', function (e) {
    $(document.documentElement).toggleClass('my-true-hover', e.trueHover);
});

это общая проблема без идеального решения. Поведение наведения полезно с помощью мыши и в основном вредно с прикосновением. Усугубляют проблему устройства, которые поддерживают touch и mouse (одновременно, не меньше!) как пиксел и поверхность Chromebook.

самое чистое решение, которое я нашел, - это включить только поведение наведения, если устройство не поддерживает сенсорный ввод.

var isTouch =  !!("ontouchstart" in window) || window.navigator.msMaxTouchPoints > 0;

if( !isTouch ){
    // add class which defines hover behavior
}

предоставлено, вы теряете наведение на устройства, которые могут его поддерживать. Однако, иногда наведение влияет больше, чем сама ссылка, например, возможно, вы хотите показать меню при наведении элемента. Такой подход позволяет проверить наличие касания и, возможно, условно прикрепить другое событие.

я протестировал это на iPhone, iPad, Chromebook Pixel, Surface и различных устройствах Android. Я не могу гарантировать, что он будет работать, когда общий сенсорный вход USB (например, стилус) добавляется в микс.


С Modernizr вы можете нацелить свои зависания специально для устройств без касания:

(Примечание: это не работает в системе фрагментов StackOverflow, проверьте jsfiddle а)

/* this one is sticky */
#regular:hover, #regular:active {
  opacity: 0.5;
}

/* this one isn't */
html.no-touch #no-touch:hover, #no-touch:active {
  opacity: 0.5;
}

отметим, что :active Не нужно быть нацеленным на .no-touch потому что он работает как на мобильных и настольных.


С 4 способа борьбы с липким наведением на мобильный: вот способ динамически добавлять или удалять "can touch" класс документа на основе текущего типа ввод пользователя. Он также работает с гибридными устройствами, где пользователь может переключаться между касанием и мышью / трекпадом:

<script>

;(function(){
    var isTouch = false //var to indicate current input type (is touch versus no touch) 
    var isTouchTimer 
    var curRootClass = '' //var indicating current document root class ("can-touch" or "")

    function addtouchclass(e){
        clearTimeout(isTouchTimer)
        isTouch = true
        if (curRootClass != 'can-touch'){ //add "can-touch' class if it's not already present
            curRootClass = 'can-touch'
            document.documentElement.classList.add(curRootClass)
        }
        isTouchTimer = setTimeout(function(){isTouch = false}, 500) //maintain "istouch" state for 500ms so removetouchclass doesn't get fired immediately following a touch event
    }

    function removetouchclass(e){
        if (!isTouch && curRootClass == 'can-touch'){ //remove 'can-touch' class if not triggered by a touch event and class is present
            isTouch = false
            curRootClass = ''
            document.documentElement.classList.remove('can-touch')
        }
    }

    document.addEventListener('touchstart', addtouchclass, false) //this event only gets called when input type is touch
    document.addEventListener('mouseover', removetouchclass, false) //this event gets called when input type is everything from touch to mouse/ trackpad
})();

</script>

$("#elementwithhover").click(function() { 
  // code that makes element or parent slide or otherwise move out from under mouse. 

  $(this).clone(true).insertAfter($(this));
  $(this).remove();
});

я собирался опубликовать свое собственное решение, но, проверив, опубликовал ли его кто-то, я обнаружил, что @Rodney почти сделал это. Тем не менее, он пропустил последний решающий момент, который сделал его uniseful, по крайней мере, в моем случае. Я имею в виду, я тоже взял то же самое .fakeHover добавление / удаление класса через mouseenter и mouseleave обнаружение событий, но только это,per se, действует почти точно так же, как" подлинный":hover. Я имею в виду: когда вы нажмете на элемент в таблице, он не обнаружит, что вы "оставили" его - таким образом, сохраняя состояние "fakehover".

то, что я сделал, было просто слушать click тоже, поэтому, когда я "нажимаю" кнопку, я вручную запускаю mouseleave.

Si это мой окончательный код:

.fakeHover {
    background-color: blue;
}


$(document).on('mouseenter', 'button.myButton',function(){
    $(this).addClass('fakeHover');
});

$(document).on('mouseleave', 'button.myButton',function(){
    $(this).removeClass('fakeHover');
});

$(document).on('button.myButton, 'click', function(){
    $(this).mouseleave();
});

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


Это было полезно для меня: ссылке

function hoverTouchUnstick() {
    // Check if the device supports touch events
    if('ontouchstart' in document.documentElement) {
        // Loop through each stylesheet
        for(var sheetI = document.styleSheets.length - 1; sheetI >= 0; sheetI--) {
            var sheet = document.styleSheets[sheetI];
            // Verify if cssRules exists in sheet
            if(sheet.cssRules) {
                // Loop through each rule in sheet
                for(var ruleI = sheet.cssRules.length - 1; ruleI >= 0; ruleI--) {
                    var rule = sheet.cssRules[ruleI];
                    // Verify rule has selector text
                    if(rule.selectorText) {
                        // Replace hover psuedo-class with active psuedo-class
                        rule.selectorText = rule.selectorText.replace(":hover", ":active");
                    }
                }
            }
        }
    }
}

добавьте этот код JS на свою страницу:

document.body.className = 'ontouchstart' in document.documentElement ? '' : 'hover';

теперь в вашем CSS перед каждым наведением добавьте класс наведения, как это:

.hover .foo:hover {}

Если устройство touch, класс тела будет пустым, в противном случае его класс будет наведен и применяются правила!


я мог бы добавить класс без наведения для каждой кнопки и сделать мой CSS как >this: button:not(.no-hover): hover { цвет фона: синий;} но это, вероятно, довольно плохо для производительности и не обрабатывает >устройства, такие как Chromebook Pixel (который имеет сенсорный экран и мышь) >правильно.

это правильная отправная точка. Следующий шаг: применить / удалить класс nohover для следующих событий (демо с jQuery)

buttonelement
 .on("touchend touchcancel",function(){$(this).addClass("nohover")})
 .on("touchstart mouseover",function({$(this).removeClass("nohover")});   

Примечание: Если Вы хотите применить другие классы к элементу buttonelement, the: not(.nohover) в CSS больше не будет работать, как ожидалось. Чем вы должны добавить вместо этого отдельное определение со значением по умолчанию и !важный тег для перезаписи стиля наведения: .nohover{ background-цвет: белый !важно}

Это должно даже обрабатывать устройства, такие как Chromebook Pixel (который имеет как сенсорный экран, так и мышь) правильно! И я не думаю, что это серьезное выступление убийцы...


решение, которое сработало для меня:

html {
   -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

добавьте этот код в таблицу стилей.

Я хотел избавиться от серого фона, который появляется на iOS Safari при нажатии ссылки. Но, похоже, он делает больше. Теперь, нажав кнопку (с :hover псевдокласса!) открывается сразу же! Я тестировал его только на iPad, я не знаю, будет ли он работать на других устройствах.


это то, что я придумал до сих пор после изучения остальные ответы. Он должен поддерживать только сенсорные, только мыши или гибридные пользователи.

создайте отдельный класс наведения для эффекта наведения. По умолчанию добавьте этот класс hover в нашу кнопку.

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

следующая проблема, что если пользователь хочет вернуться к использованию мыши после нажатия кнопки? Чтобы решить эту проблему, нам нужно найти подходящий момент, чтобы добавить обратно класс наведения, который мы удалили.

однако мы не можем добавить его обратно сразу после его удаления, потому что состояние наведения все еще активно. Мы можем не хотеть разрушать и воссоздайте всю кнопку.

Итак, я подумал об использовании алгоритма ожидания занятости (с помощью setInterval) для проверки состояния наведения. Как только состояние наведения отключено, мы можем добавить обратно класс наведения и остановить ожидание занятости, возвращая нас в исходное состояние, где пользователь может использовать мышь или касание.

Я знаю, что занят-ожидание не так здорово, но я не уверен, что есть соответствующее событие. Я думал добавить его обратно в событие mouseleave, но он был не очень крепким. Например, когда после нажатия кнопки появляется предупреждение, положение мыши меняется, но событие mouseleave не запускается.

var button = document.getElementById('myButton');

button.ontouchstart = function(e) {
  console.log('ontouchstart');
  $('.button').removeClass('button-hover');
  startIntervalToResetHover();
};

button.onclick = function(e) {
  console.log('onclick');
}

var intervalId;

function startIntervalToResetHover() {
  // Clear the previous one, if any.
  if (intervalId) {
    clearInterval(intervalId);
  }
  
  intervalId = setInterval(function() {
    // Stop if the hover class already exists.
    if ($('.button').hasClass('button-hover')) {
      clearInterval(intervalId);
      intervalId = null;
      return;
    }
    
    // Checking of hover state from 
    // http://stackoverflow.com/a/8981521/2669960.
    var isHovered = !!$('.button').filter(function() {
      return $(this).is(":hover");
    }).length;
    
    if (isHovered) {
      console.log('Hover state is active');
    } else {
      console.log('Hover state is inactive');
      $('.button').addClass('button-hover');
      console.log('Added back the button-hover class');
      
      clearInterval(intervalId);
      intervalId = null;
    }
  }, 1000);
}
.button {
  color: green;
  border: none;
}

.button-hover:hover {
  background: yellow;
  border: none;
}

.button:active {
  border: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id='myButton' class='button button-hover'>Hello</button>

Edit: другой подход, который я пробовал, - это вызов e.preventDefault() внутри ontouchstart или ontouchend. Кажется, что он останавливает эффект наведения при касании кнопки, но он также останавливает анимацию нажатия кнопки и предотвращает вызов функции onclick, когда кнопка коснулся, поэтому вам нужно вызвать их вручную в обработчике ontouchstart или ontouchend. Не очень чистое решение.


вы можете установить цвет фона на :active государство и дать :focus по умолчанию фон.

если вы установите цвет фона через onfocus/ontouch, цвет стиль остается после :focus государство пошло.
Вам нужен сброс на onblur а также восстановить defaut bg, когда фокус потерян.


это сработало для меня: поместите стиль наведения в новый класс

.fakehover {background: red}

затем добавьте / удалите класс по мере необходимости

$(".someclass > li").on("mouseenter", function(e) {
  $(this).addClass("fakehover");
});
$(".someclass > li").on("mouseleave", function(e) {
  $(this).removeClass("fakehover");
});

повторите для событий touchstart и touchend. Или любые события, которые вы хотите получить желаемый результат, например, я хотел, чтобы эффект наведения переключался на сенсорном экране.


Я думаю, что нашел элегантное (минимальное js) решение для аналогичной проблемы:

используя jQuery, вы можете вызвать наведение на тело (или любой другой элемент), используя .mouseover()

поэтому я просто прикрепляю этот обработчик к элементу ontouchend событие вот так:

var unhover = function() {
  $("body").mousover();  
};
.hoverable {
  width: 100px;
  height: 100px;
  background: teal;
  cursor: pointer;
}

.hoverable:hover {
  background: pink;
}
<div class="hoverable" ontouchend={unhover}></div>

это, однако, только удаляет: наведите псевдокласс из элемента после того, как было вызвано какое-либо другое событие касания, например swipe или другое касание


на основе ответа Darren Cooks, который также работает, если вы переместили палец на другой элемент.

посмотреть найти элемент палец на touchend событие

jQuery(function() {
    FastClick.attach(document.body);
});
// Prevent sticky hover effects for buttons on touch devices
// From https://stackoverflow.com/a/17234319
//
//
// Usage:
// <a href="..." touch-focus-fix>..</a>
//
// Refactored from a directive for better performance and compability
jQuery(document.documentElement).on('touchend', function(event) {
  'use strict';

  function fix(sourceElement) {
    var el = $(sourceElement).closest('[touch-focus-fix]')[0];
    if (!el) {
      return;
    }
    var par = el.parentNode;
    var next = el.nextSibling;
    par.removeChild(el);
    par.insertBefore(el, next);
  }

  fix(event.target);
  var changedTouch = event.originalEvent.changedTouches[0];
  // http://www.w3.org/TR/2011/WD-touch-events-20110505/#the-touchend-event
  if (!changedTouch) {
    return;
  }
  var touchTarget = document.elementFromPoint(changedTouch.clientX, changedTouch.clientY);
  if (touchTarget && touchTarget !== event.target) {
    fix(touchTarget);
  }
});

Codepen Demo


Вы можете попробовать этот способ.

в JavaScript:

var isEventSupported = function (eventName, elementName) {
    var el = elementName ? document.createElement(elementName) : window;
    eventName = 'on' + eventName;
    var isSupported = (eventName in el);
    if (!isSupported && el.setAttribute) {
        el.setAttribute(eventName, 'return;');
        isSupported = typeof el[eventName] == 'function';
    }
    el = null;
    return isSupported;
};

if (!isEventSupported('touchstart')) {
    $('a').addClass('with-hover');
}

css:

a.with-hover:hover {
  color: #fafafa;
}

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

.myhoveredclass {
    background-color:green;
}
.myhoveredclass:hover {
    background-color:red;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
    .myhoveredclass:hover, .myhoveredclass:active, .myhoveredclass:focus {
        background-color:green;
    }
}

все имена классов и имени цвета только для демонстрационных целей ;-)


это прекрасно работает в 2 шага.

  1. установите тег тела, чтобы быть таким <body ontouchstart="">. Я не поклонник этого "взлома", но он позволяет Safari на iOS мгновенно реагировать на прикосновения. Не знаю как, но это работает.

  2. настройте свой сенсорный класс следующим образом:

    // I did this in SASS, but this should work with normal CSS as well
    
    // Touchable class
    .example {
    
        // Default styles
        background: green;
    
        // Default hover styles 
        // (Think of this as Desktop and larger)
        &:hover {
            background: yellow;
        }
    
        // Default active styles
        &:active {
            background: red;
        }
    
        // Setup breakpoint for smaller device widths
        @media only screen and (max-width: 1048px) {
    
            // Important!
            // Reset touchable hover styles
            // You may want to use the same exact styles as the Default styles
            &:hover {
                background: green;
            }
    
            // Important!
            // Touchable active styles
            &:active {
                background: red;
            }
        }
    }
    

вы также можете удалить любую анимацию в своем сенсорном классе. Android Chrome кажется немного медленнее, чем усвн.

это также приведет к применению активного состояния, если пользователь прокручивает страницу, касаясь вашего класса.