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


Я создал карусель с предыдущей и следующей кнопки, которые видны всегда. Эти кнопки имеют состояние наведения, они становятся синими. На сенсорных устройствах, таких как 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 107

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 Media Queries Level 4 реализовано, вы сможете сделать это:

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

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

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

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

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

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

Это распространенная проблема без идеального решения. Поведение при наведении полезно с помощью мыши и в основном вредно с прикосновением. Усугубляют проблему устройства, которые поддерживают сенсорный и мышь (одновременно, не меньше!) как пиксель и поверхность 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 {}

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

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

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

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

Обратите Внимание: Если Вы хотите применить другие классы к элементу buttonelement, то: 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>

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

на основе ответа 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, кажется, немного медленнее, чем усвн.

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