web-dev-qa-db-ja.com

iPad / iPhoneのホバー問題により、ユーザーがリンクをダブルクリックする

私はjqueryマウスイベントを使用するいくつかのウェブサイトを以前に構築しました...私はiPadを手に入れましたが、すべてのマウスオーバーイベントがクリックで翻訳されることに気づきました...たとえば、1回ではなく2回クリックする必要があります..(実際のクリックよりも最初のホバー)

これを解決する準備ができていますか?たぶん、マウスオーバー/アウトなどの代わりに私が使用しているjqueryコマンドです。ありがとう!

119
Francesco

これを完全にはテストしていませんが、iOSがタッチイベントを起動するので、jQueryの設定になっていると仮定すると、これは機能します。

$('a').on('click touchend', function(e) {
    var el = $(this);
    var link = el.attr('href');
    window.location = link;
});

アイデアは、Mobile WebKitがタップの最後にtouchendイベントを発生させるので、それをリッスンし、リンク上でtouchendイベントが発生するとすぐにブラウザーをリダイレクトします。

197
cduruk

あなたの質問が何であるかは完全に明確ではありませんが、マウスのホバー効果を維持しながらダブルクリックを排除したい場合、私のアドバイスは次のとおりです:

  • touchstartおよびmouseenterにホバー効果を追加します。
  • mouseleavetouchmove、およびclickのホバー効果を削除します。

バックグラウンド

マウスをシミュレートするために、ユーザーがタッチスクリーン(iPadなど)でタッチして指を離すと、Webkitモバイルなどのブラウザーは次のイベントを起動します(ソース: Touch And Mouse on html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. ブラウザがダブルタップではなくシングルタップであることを確認する300ミリ秒の遅延
  5. mouseover
  6. mouseenter
    • mouseovermouseenter、またはmousemoveイベントがページコンテンツを変更する場合、以下のイベントは発生しません。
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

単にマウスイベントをスキップするようにウェブブラウザに指示することは不可能と思われます。

さらに悪いことに、マウスオーバーイベントがページコンテンツを変更した場合、 Safari Webコンテンツガイド-イベントの処理 で説明されているように、クリックイベントは発生しません。特に、One -指イベント。まさに「コンテンツの変更」とは、ブラウザーとバージョンによって異なります。 iOS 7.0の場合、背景色の変更はコンテンツの変更ではない(またはもはやない)ことがわかりました。

ソリューションの説明

要点をまとめると:

  • touchstartおよびmouseenterにホバー効果を追加します。
  • mouseleavetouchmove、およびclickのホバー効果を削除します。

touchendにはアクションがないことに注意してください!

これは、マウスイベントに対して明らかに機能します。mouseenterおよびmouseleavemouseoverおよびmouseoutのわずかに改善されたバージョン)が起動され、ホバーを追加および削除します。

ユーザーが実際にリンクをclicksした場合、ホバー効果も削除されます。これにより、ユーザーがWebブラウザーの戻るボタンを押すと、確実に削除されます。

これはタッチイベントでも機能します。touchstartにホバー効果が追加されます。 touchendでは '' 'not' ''が削除されます。これはmouseenterに再び追加され、これによりコンテンツの変更が発生しないため(既に追加されている)、clickイベントも発生し、ユーザーが再度クリックする必要なくリンクがたどられます!

ブラウザーがtouchstartイベントとclickの間に持つ300ミリ秒の遅延は、ホバー効果がこの短い時間の間に表示されるため、実際に有効に使用されます。

ユーザーがクリックをキャンセルすることを決定した場合、指を動かすと通常どおりにキャンセルされます。通常、これはmouseleaveイベントが発生しないため問題であり、ホバー効果はそのまま残ります。ありがたいことに、これはtouchmoveのホバー効果を削除することで簡単に修正できます。

それでおしまい!

たとえば、 FastClickライブラリ を使用して300ミリ秒の遅延を削除することもできますが、これはこの質問の範囲外です。

代替ソリューション

私は、次の代替案で次の問題を発見しました。

  • ブラウザの検出:非常にエラーが発生しやすい。デバイスにマウスまたはタッチのいずれかがあると仮定しますが、タッチディスプレイが普及すると、両方の組み合わせがますます一般的になります。
  • CSSメディア検出:私が知っている唯一のCSSのみのソリューション。それでもエラーが発生しやすく、デバイスにはマウスまたはタッチのいずれかがあり、両方とも可能であると想定されます。
  • touchendでクリックイベントをエミュレートします。ユーザーが実際にクリックする意図なしに、スクロールまたはズームのみしたい場合でも、これはリンクを誤ってたどります。リンク。
  • 変数を使用してマウスイベントを抑制します:これにより、変数がtouchendに設定され、後続のマウスイベントでif条件として使用され、状態の変化を防ぎますある時点。変数はクリックイベントでリセットされます。これは、タッチインターフェイスでホバー効果を本当に必要としない場合、適切なソリューションです。残念ながら、touchendが別の理由で発生し、クリックイベントが発生しない場合(たとえば、ユーザーがスクロールまたはズームした場合)は機能しません。その後、マウスでリンクをたどろうとしますインタフェース)。

参考文献

iPad/iPhoneダブルクリックの問題 および モバイルブラウザーでホバー効果を無効にする も参照してください。

36
MacFreek

結局CSSソリューションがあるようです。 Safariが2回目のタッチを待機する理由は、通常:hoverイベントで割り当てる背景画像(または要素)のためです。表示されるものがない場合-問題はありません。解決策は、たとえば、継承するためにホバー背景をオーバーライドし、マウスオーバーで表示する要素を非表示にしておくセカンダリCSSファイル(またはJSアプローチの場合はスタイル)でiOSプラットフォームをターゲットにすることです。

CSSとHTMLの例を次に示します-マウスオーバーに星印の付いた製品ブロック:

HTML:

<a href="#" class="s"><span class="s-star"></span>Some text here</a>

CSS:

.s {
   background: url(some-image.png) no-repeat 0 0;

}
.s:hover {
   background: url(some-image-r.png) no-repeat 0 0;
}

.s-star {
   background: url(star.png) no-repeat 0 0;
   height: 56px;
   position: absolute;
   width: 72px;
   display:none;
}

.s:hover .s-star {
   display:block;
}

ソリューション(セカンダリCSS):

/* CSS */
/* Keep hovers the same or hidden */
.s:hover {
   background:inherit;
}
.s:hover .s-star {
   display:none;
}
20
Pavel Bogatinov

私のために働いたのは、ここの他の人がすでに言ったことです:

ホバーまたはマウス移動で要素を表示/非表示にしないでください(これは私の場合のイベントです)。

Appleの内容は次のとおりです( https://developer.Apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html ):

クリック可能な要素は、リンク、フォーム要素、イメージマップ領域、またはmousemove、mousedown、mouseup、またはonclickハンドラーを持つ他の要素です。

ユーザーがクリック可能な要素をタップすると、イベントはmouseover、mousemove、mousedown、mouseup、clickの順に到着します。また、mousemoveイベントでページのコンテンツが変更された場合、シーケンス内の後続のイベントは送信されません。この動作により、ユーザーは新しいコンテンツ。

したがって、@ woopのソリューションを使用できます。userAgentを検出し、それがiOSデバイスかどうかを確認してから、イベントをバインドします。私がこのテクニックを使用することになったのは、それが私のニーズに合っていて、望まないときにホバーイベントをバインドしないほうが理にかなっているからです。

しかし... userAgentsをいじりたくなくても、hover/mousemoveで要素を非表示/表示する場合は、次のようにネイティブjavascriptを使用して行うことができることがわかりました。

$("body").on("mouseover", function() {
       document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
       document.querySelector(".my-selector div").style.display = 'none'; // hide element
});

これは、デスクトップバージョンでは機能しますが、モバイルバージョンでは機能しません。

そして、もう少し互換性のために...

$("body").on("mouseover", function() {
   if (document.getElementsByTagName && document.querySelector) { // check compatibility
       document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
       document.querySelector(".my-selector div").style.display = 'none'; // hide element
    } else {
        $(".my-class").show();
        $(".my-selector div").hide();
    }
});
5
Adauto

cdurukのソリューションは非常に効果的でしたが、私のサイトのいくつかの部分で問題を引き起こしました。私はすでにjQueryを使用してCSSホバークラスを追加していたため、最も簡単な解決策は、CSSホバークラスをモバイルデバイスに追加しないことです(より正確には、モバイルデバイス上にない場合にのみ追加します)。

一般的なアイデアは次のとおりです。

var device = navigator.userAgent.toLowerCase();
var ios = device.match(/(iphone|iPod|ipad)/);

if (!(ios)) {
    $(".portfolio-style").hover(
        function(){
            $(this).stop().animate({opacity: 1}, 100);
            $(this).addClass("portfolio-red-text");
        },
        function(){
            $(this).stop().animate({opacity: 0.85}, 100);
            $(this).removeClass("portfolio-red-text");
        }
    );
}

*説明のためにコードを削減

3
woop

過度に複雑にする必要はありません。

$('a').on('touchend', function() {
    $(this).click();
});
3
Jsonras

mouseenterの代わりにmouseoverを試すのが賢明だと思います。これは、.hover(fn,fn)にバインドするときに内部的に使用されるものであり、通常は必要なものです。

2
Paul Irish

MacFreakの答えは私にとって非常に役に立ちました。役立つ場合の実践的なコードを次に示します。

PROBLEM-適用touchendは、要素上で指をスクロールするたびに、過去にスクロールしようとしても、押したように応答することを意味します。

JQueryを使用して、いくつかのボタンの下の行をフェードアップして、ホバーされたボタンを「強調表示」するエフェクトを作成しています。リンクをたどるためにタッチデバイスでボタンを2回押す必要があるという意味ではありません。

ボタンは次のとおりです。

<a class="menu_button" href="#">
    <div class="menu_underline"></div>
</a>

「menu_underline」divは、マウスオーバーでフェードアップし、マウスアウトでフェードアウトするようにします。しかし、タッチデバイスが2回ではなく1回のクリックでリンクをたどることができるようにしたいと思います。

SOLUTION-これを機能させるjQueryは次のとおりです。

//Mouse Enter
$('.menu_button').bind('touchstart mouseenter', function(){
    $(this).find(".menu_underline").fadeIn();
});

//Mouse Out   
$('.menu_button').bind('mouseleave touchmove click', function(){
    $(this).find(".menu_underline").fadeOut();
});

このMacFreakに関するご協力に感謝します。

1
Mad Dog Cadogen

既存のソリューションには次の問題があり、それらすべてを解決するように思われる何かを見つけました。これは、クロスブラウザ、クロスデバイスを狙っており、デバイスのスニッフィングを望まないことを前提としています。

これが解決する問題

touchstartまたはtouchendのみを使用:

  • 人々がコンテンツを過ぎてスクロールしようとするで、スワイプを開始したときにたまたまこの要素の上に指があると、イベントが発生します-予期せずアクションをトリガーします。
  • デスクトップを右クリックした場合と同様に、イベントが発生しますon longpress。たとえば、クリックイベントがURL Xに移動し、ユーザーが長押しして新しいタブでXを開くと、ユーザーは両方のタブでXが開いているのを見つけるのに混乱します。一部のブラウザ(iPhoneなど)では、長押しメニューが表示されないことさえあります。

mouseovertouchstartイベントをトリガーし、mouseouttouchmoveイベントをトリガーしても、重大な結果は少なくなりますが、通常のブラウザーの動作には干渉します。例:

  • 長押しすると、マウスオーバーがトリガーされて終了しません。
  • 多くのAndroidブラウザーは、touchstartの指の位置をmouseoverのように扱います。これは、次のmouseouttouchstartedになります。したがって、Androidでマウスオーバーコンテンツを表示する1つの方法は、関心のある領域に触れて指を小刻みに動かし、ページをわずかにスクロールすることです。 touchmovemouseoutとして扱うと、これが壊れます。

ソリューション

理論的には、touchmoveでフラグを追加することはできますが、動きがない場合でもiPhoneはtouchmoveをトリガーします。理論的には、touchstarttouchendのイベントpageXpageYを比較できます ただし、iPhoneにはtouchendpageXまたはpageYはありません

残念ながら、すべてのベースをカバーするために、やや複雑になります。

$el.on('touchstart', function(e){
    $el.data('tstartE', e);
    if(event.originalEvent.targetTouches){
        // store values, not reference, since touch obj will change
        var touch = e.originalEvent.targetTouches[0];
        $el.data('tstartT',{ clientX: touch.clientX, clientY: touch.clientY } );
    }
});
$el.on('touchmove', function(e){
    if(event.originalEvent.targetTouches){
        $el.data('tstartM', event.originalEvent.targetTouches[0]);
    }
});

$el.on('click touchend', function(e){
    var oldE = $el.data('tstartE');
    if( oldE && oldE.timeStamp + 1000 < e.timeStamp ) {
        $el.data('tstartE',false);
        return;
    }
    if( $el.data('iosTouchM') && $el.data('tstartT') ){
        var start = $el.data('tstartT'), end = $el.data('tstartM');
        if( start.clientX != end.clientX || start.clientY != end.clientY ){
            $el.data('tstartT', false);
            $el.data('tstartM', false);
            $el.data('tstartE',false);
            return;
        }
    }
    $el.data('tstartE',false);

理論的には、 近似値として1000を使用する代わりに、長押しに使用される正確な時間を取得する方法がありますが、実際にはそれほど単純ではなく、合理的なプロキシを使用するのが最善です

1
user568458

リンクにはonmouseoverイベントがありません。1タップでonmouseoverがアクティブになり、ダブルタップでリンクがアクティブになります。しかし、idk。 iPadを持っていません。ジェスチャー/タッチイベントを使用する必要があると思います。

https://developer.Apple.com/documentation/webkitjs

1
albert

空のリスナーを追加しても機能することがわかりました。理由を聞かないでください。ただし、iOS 9.3.2を搭載したiPhoneおよびiPadでテストし、正常に機能しました。

if(/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream){
    var elements = document.getElementsByTagName('a');
    for(var i = 0; i < elements.length; i++){
        elements[i].addEventListener('touchend',function(){});
    }
}
1

この短いスニペットは機能しているようです。リンクがタップされたときにクリックイベントをトリガーします。

  $('a').on('touchstart', function() {
    $(this).trigger('click');
  });
0
Flo Develop

誤ってリンク上で指をスライドさせたときにリダイレクトを回避するための改善点です。

// tablet "one touch (click)" X "hover" > link redirection
$('a').on('touchmove touchend', function(e) {

    // if touchmove>touchend, set the data() for this element to true. then leave touchmove & let touchend fail(data=true) redirection
    if (e.type == 'touchmove') {
        $.data(this, "touchmove_cancel_redirection", true );
        return;
    }

    // if it's a simple touchend, data() for this element doesn't exist.
    if (e.type == 'touchend' && !$.data(this, "touchmove_cancel_redirection")) {
        var el = $(this);
        var link = el.attr('href');
        window.location = link;
    }

    // if touchmove>touchend, to be redirected on a future simple touchend for this element
    $.data(this, "touchmove_cancel_redirection", false );
});
0
Lenor

MacFreakからインスピレーションを得て、自分に合ったものをまとめました。

このjsメソッドは、ホバーがiPadに貼り付かないようにし、クリックが2回のクリックとして登録されるのを防止します。 CSSで、CSSに:hover psudoクラスがある場合、それらを.hoverに変更します。たとえば、.some-class:hoverを.some-class.hoverに変更します

Ipadでこのコードをテストして、cssおよびjsホバーメソッドの動作の違いを確認します(ホバー効果のみ)。 CSSボタンには、派手なクリックアラートはありません。 http://jsfiddle.net/bensontrent/ctgr6stm/

function clicker(id, doStuff) {
  id.on('touchstart', function(e) {
    id.addClass('hover');
  }).on('touchmove', function(e) {
    id.removeClass('hover');
  }).mouseenter(function(e) {
    id.addClass('hover');
  }).mouseleave(function(e) {
    id.removeClass('hover');
  }).click(function(e) {
    id.removeClass('hover');
    //It's clicked. Do Something
    doStuff(id);
  });
}

function doStuff(id) {
  //Do Stuff
  $('#clicked-alert').fadeIn(function() {
    $(this).fadeOut();
  });
}
clicker($('#unique-id'), doStuff);
button {
  display: block;
  margin: 20px;
  padding: 10px;
  -webkit-appearance: none;
  touch-action: manipulation;
}
.hover {
  background: yellow;
}
.btn:active {
  background: red;
}
.cssonly:hover {
  background: yellow;
}
.cssonly:active {
  background: red;
}
#clicked-alert {
  display: none;
}
<button id="unique-id" class="btn">JS Hover for Mobile devices<span id="clicked-alert"> Clicked</span>

</button>
<button class="cssonly">CSS Only Button</button>
<br>This js method prevents hover from sticking on an ipad, and prevents the click registering as two clicks. In CSS, if you have any :hover in your css, change them to .hover For example .some-class:hover to .some-class.hover
0
Benson

私は同じ問題を抱えていましたが、タッチデバイスではありませんでした。クリックするたびにイベントがトリガーされます。イベントのキューイングなどについて何かがあります。

しかし、私の解決策は次のようなものでした:クリックイベント(またはタッチ?)でタイマーを設定します。 X ms以内にリンクが再度クリックされた場合、falseを返します。

要素ごとのタイマーを設定するには、$.data()を使用できます。

これにより、上記の@Ferdyの問題も解決される場合があります。

0
Ionuț Staicu

Modernizrを使用する場合、前述のModernizr.touchを使用するのは非常に簡単です。

ただし、安全のために、Modernizr.touchとユーザーエージェントテストの組み合わせを使用することをお勧めします。

var deviceAgent = navigator.userAgent.toLowerCase();

var isTouchDevice = Modernizr.touch || 
(deviceAgent.match(/(iphone|iPod|ipad)/) ||
deviceAgent.match(/(Android)/)  || 
deviceAgent.match(/(iemobile)/) || 
deviceAgent.match(/iphone/i) || 
deviceAgent.match(/ipad/i) || 
deviceAgent.match(/iPod/i) || 
deviceAgent.match(/blackberry/i) || 
deviceAgent.match(/bada/i));

function Tipsy(element, options) {
    this.$element = $(element);
    this.options = options;
    this.enabled = !isTouchDevice;
    this.fixTitle();
};

Modernizrを使用しない場合は、上記のModernizr.touch関数を('ontouchstart' in document.documentElement)に置き換えるだけです。

また、ユーザーエージェントiemobileをテストすると、検出されたMicrosoftモバイルデバイスの範囲がWindows Phoneよりも広いことに注意してください。

0
Peter-Pan

click touchendを使用できます

例:

$('a').on('click touchend', function() {
    var linkToAffect = $(this);
    var linkToAffectHref = linkToAffect.attr('href');
    window.location = linkToAffectHref;
});

上記の例は、タッチデバイス上のすべてのリンクに影響します。

特定のリンクのみをターゲットにしたい場合は、それらにクラスを設定することでこれを行うことができます。すなわち:

HTML:

<a href="example.html" class="prevent-extra-click">Prevent extra click on touch device</a>

Jquery:

$('a.prevent-extra-click').on('click touchend', function() {
    var linkToAffect = $(this);
    var linkToAffectHref = linkToAffect.attr('href');
    window.location = linkToAffectHref;
});

乾杯、

イェロエン

0
Jeroen W

タッチスクロールを中断せずにリンクを機能させるには、jQuery Mobileの「タップ」イベントでこれを解決します。

    $('a').not('nav.navbar a').on("tap", function () {
        var link = $(this).attr('href');
        if (typeof link !== 'undefined') {
            window.location = link;
        }
    });
0
mooses

イベントが要素のmouseenter/mouseleave/click状態にバインドされているのに似た状況に陥りましたが、iPhoneでは、ユーザーが要素をダブルクリックして最初にmouseenterイベントをトリガーし、次に再度clickイベントを発生させる必要がありました。

上記と同様の方法を使用してこれを解決しましたが、次のように、jQuery $ .browserプラグイン(jQuery 1.9>の場合)を使用し、mouseenterバインディングイベントに.triggerイベントを追加しました。

// mouseenter event
$('.element').on( "mouseenter", function() {
    // insert mouseenter events below

    // double click fix for iOS and mouseenter events
    if ($.browser.iphone || $.browser.ipad) $(this).trigger('click');
});
// mouseleave event
$('.element').on( "mouseleave", function() { 
    // insert mouseout events below
});
// onclick event
$('.element').on( "click", function() {
    // insert click events below
});

.triggerは、iPhoneまたはiPadで表示されている要素のmouseenter(または最初のクリック)時に.clickイベントハンドラーを起動することにより、要素をダブルクリックする必要を防ぎます。最もエレガントなソリューションではないかもしれませんが、私の場合はうまく機能し、既に配置されているプラ​​グインを利用し、これらのデバイスで既存のイベントを動作させるためにコードを1行追加する必要がありました。

ここでjQuery $ .browserプラグインを取得できます: https://github.com/gabceb/jquery-browser-plugin

0
ouija

これは、jquery uiドロップダウンがあるときに機能します

if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
      $('.ui-autocomplete').off('menufocus hover mouseover');
}
0
Makubex

他の答えのどれも私にはうまくいきません。私のアプリには、多くのイベントリスナー、独自のチェックボックス、およびリスナーとリスナーのないリンクを持つリンクがあります。

私はこれを使用します:

var selector = "label, a, button";
var timer;
var startX;
var startY;
$(document).on("click", selector, function (e) {
    if ($(this).data("touched") === true) {
        e.stopImmediatePropagation();
        return false;
    }
    return;
}).on("touchend", selector, function (e) {
    if (Math.abs(startX - e.originalEvent.changedTouches[0].screenX) > 10 || Math.abs(startY - e.originalEvent.changedTouches[0].screenY) > 10)
        // user action is not a tap
        return;
    var $this = $(this);
    // Visit: http://stackoverflow.com/questions/1694595/can-i-call-jquery-click-to-follow-an-a-link-if-i-havent-bound-an-event-hand/12801548#12801548
    this.click();
    // prevents double click
    $this.data("touched", true);
    if (timer)
        clearTimeout(timer);
    setTimeout(function () {
        $this.data("touched", false);
    }, 400);
    e.stopImmediatePropagation();
    return false;
}).on("touchstart", function (e) {
    startX = e.originalEvent.changedTouches[0].screenX;
    startY = e.originalEvent.changedTouches[0].screenY;
});
0
Magu

私は遅すぎます、私は知っていますが、これは私が見つけた最も簡単な回避策の1つです:

    $('body').on('touchstart','*',function(){   //listen to touch
        var jQueryElement=$(this);  
        var element = jQueryElement.get(0); // find tapped HTML element
        if(!element.click){
            var eventObj = document.createEvent('MouseEvents');
            eventObj.initEvent('click',true,true);
            element.dispatchEvent(eventObj);
        }
    });

これは、リンク(アンカータグ)だけでなく、他の要素でも機能します。お役に立てれば。

0
writeToBhuwan