onclick()およびonblur()の順序付けの問題


102

カスタムドロップダウンメニューを表示する入力フィールドがあります。次の機能が必要です。

  • ユーザーが入力フィールドの外側をクリックすると、メニューが削除されます。
  • 場合は、より具体的には、メニュー内のdiv要素をユーザーがクリックすると、メニューを削除する必要があり、かつ特別な処理がクリックされたdiv要素に基づいて行うべきです。

これが私の実装です:

入力フィールドには、ユーザーが入力フィールドの外をクリックしたときにonblur()メニューを削除する(その親innerHTMLを空の文字列に設定する)イベントがあります。メニュー内のdivにはonclick()、特別な処理を実行するイベントもあります。

問題はonclick()、入力フィールドonblur()が最初に起動され、onclick()s を含むメニューが削除されるため、メニューがクリックされたときにイベントが起動しないことです。

メニューのdiv onclick()onmousedown()onmouseup()イベントに分割し、マウスダウン時にグローバルフラグを設定することで問題を解決しました。このフラグは、この回答で提案されているものと同様です。のonmousedown()onblur()に起動するためonblur()、メニューdivの1つがクリックされた場合はフラグが設定されますが、画面の他の場所がクリックされた場合は設定されません。メニューがクリックされた場合は、メニューonblur()を削除せずにすぐに戻り、が起動するのを待ちonclick()ます。その時点で、メニューを安全に削除できます。

よりエレガントな解決策はありますか?

コードは次のようになります。

<div class="menu" onmousedown="setFlag()" onmouseup="doProcessing()">...</div>
<input id="input" onblur="removeMenu()" ... />

var mouseflag;

function setFlag() {
    mouseflag = true;
}

function removeMenu() {
    if (!mouseflag) {
        document.getElementById('menu').innerHTML = '';
    }
}

function doProcessing(id, name) {
    mouseflag = false;
    ...
}

回答:


167

私はあなたとまったく同じ問題を抱えていました、私のUIはあなたが説明するとおりに設計されています。onClickメニュー項目のをに置き換えるだけで問題が解決しましたonMouseDown。私は他に何もしませんでした。いいえonMouseUp、フラグはありません。これにより、追加の作業なしで、ブラウザーにこれらのイベントハンドラーの優先度に基づいて自動的に再配列させることで問題が解決しました。

これでもうまくいかなかった理由はありますか?


3
これは実際に機能します。私はそれをFFでテストしました、そしてそれは魅力のように働きます。
Supreme Dolphin

3
できます。Firefox、Chrome、Internet Explorerでテストonmousedownされる前に実行されるようですonblur
Tonatio

11
これにより、動作が少し自然に変わることに注目してください。クリック操作は、マウスアップではなくマウスダウンで処理されます。大丈夫かもしれませんが(この場合は自分で)ほとんどの人にとって、いくつかの欠点があります。最も注目に値するのは、誤ってクリックした場合にボタンをクリックしてドラッグしてオフにすることです。これにより、onclickが呼び出されなくなります。ボタンがPure以外の機能(削除、投稿など)を実行する場合は、これを保持してフラグアプローチを使用できます。
ブリジー

2
あなたが代わりのonClickのmouseDownイベントを設定し、現時点でアクセシビリティについてあなたはすべての<ボタン>要素のアクセシビリティを殺す:/
ncubica

2
@ ian-macfarlaneが以下にリストする答えは、この問題に対処する正しい方法です。要約すると... onMouseDownevent.preventDefault() し、onClickご希望の作用をもちます。
Conal Trinitrotoluene Da Costa

46

onClickで置き換えないでくださいonMouseDown

このアプローチはいくらか機能しますが、2つは根本的に異なるイベントであり、ユーザーの目には異なる期待があります。この場合、のonMouseDown代わりにonClickを使用すると、ソフトウェアの予測可能性が損なわれます。したがって、2つのイベントは交換できません。

例として、誤ってボタンをクリックした場合、ユーザーはマウスクリックを押したまま要素の外にカーソルをドラッグし、マウスボタンを離すと、最終的にアクションが実行されなくなると考えています。onClickこれを行います。onMouseDownユーザーがマウスを押し続けることを許可せず、代わりにユーザーに頼ることなく、ただちにアクションをトリガーします。onClickコンピューターでアクションをトリガーするために使用する標準です。

この状況でevent.preventDefault()は、onMouseDownイベントを呼び出します。onMouseDownデフォルトではぼかしイベントが発生し、preventDefaultが呼び出された場合は発生しません。その後、onClick呼び出される機会があります。ぼかしイベントは、の後でのみ発生しonClickます。

すべての後、onClickイベントはの組み合わせですonMouseDownonMouseUp、彼らの両方が同じ要素内に発生した場合と場合にのみ、。


7
これは私にとって完璧なソリューションであり、コメントは完全に正しいです。onMouseDownを置き換えると、ユーザーエクスペリエンスが混乱します。投票した。
ポールバートレット

5
これが正解です。これを投稿していただきありがとうございます
user1189352

1
これにはいくつかのマイナーな副作用もあります。たとえば、要素内をクリックしてテキストを選択できないなどです。これが問題になるケースはあまり考えられません。ただ少しおもしろいと感じるだけです。
宮坂零

1
イベントで使用するevent.preventDefault()onMouseDown、私のonFocusイベントは呼び出されません
バットマン

4

置き換えonmousedownonfocus。したがって、このイベントは、フォーカスがテキストボックス内にあるときにトリガーされます。

置き換えonmouseuponblur。テキストボックスからフォーカスを外すと、onblurが実行されます。

これはあなたが必要とするかもしれないものだと思います。

更新

関数onfocusを実行するとき-> onblurに適用するクラスを削除し、onfocusで実行するクラスを追加します。

そして

関数onblurを実行するとき-> onfocusで適用するクラスを削除し、onblurで実行するクラスを追加します。

フラグ変数は必要ないようです。

更新2:

onmouseoutおよびonmouseoverのイベントを使用できます

onmouseover-カーソルが上にあることを検出します。

onmouseout-カーソルが離れたときを検出します。


わかりやすくするために質問を編集しました。このソリューションでは、フラグを設定する必要性をどのように回避しますか?また、私はテキストボックス/入力フィールドではなく、メニューでを使用onmousedown()onmouseup()ています。
1

正解かどうかわからないので、この回答はまだ受け入れられません。上記のコメントで私が提起した懸念に対応できますか?
1 ''

確かに、私が現在実行しているのと同様にonmouseoverとonmouseoutを使用して、マウスがメニューの上にあるときにフラグを設定する方法を確認できます。ただし、onmouseoverでフラグを設定してonmouseoutでクリアする必要があるので、クリックしたときにメニューが表示されたかどうかを確認できます。フラグを設定する必要がない方法を考えることができますか?
1 ''

私はあなたのコードを参照することができます...フィドルに入れて、私はresults..justコードを参照してくださいいけない場合...ただ、関連する部分とその完全にOKを置く...
HIRAタクール


2

よりエレガントな(ただし、パフォーマンスが低下する可能性が高い)ソリューション:

その代わり、メニュー、使用削除するには、入力のonBlurイベントを使用してのdocument.onclick後に発射し、onblur

ただし、これは、入力自体をクリックするとメニューが削除されることも意味し、これは望ましくない動作です。input.onclickwith event.stopPropagation()を設定して、クリックがドキュメントクリックイベントに伝播しないようにします。


5
しかし、この回答の問題は、ぼかしの原因となったのがクリックイベントではなかった場合です。ユーザーが入力からタブアウトした場合はどうなりますか?これにより、ぼかしイベントがトリガーされますが、フライアウトメニューは引き続き表示されます。
クリストフ

0

onfocusでonclickを変更する

onblurとonclickがうまくいかない場合でも、明らかにonfocusとyes onblurです。メニューが閉じられた後でも、onfocusは内部でクリックされた要素に対して有効です。

私はそうしました、そしてそれはうまくいきました。


-7

次のようにsetIntervalonBlurハンドラー内で関数を使用できます。

<input id="input" onblur="removeMenu()" ... />

function removeMenu() {
    setInterval(function(){
        if (!mouseflag) {
            document.getElementById('menu').innerHTML = '';
        }
    }, 0);
}

setIntervalこの関数は、あなたの削除されonBlur ます0に設定した時間、この関数はすぐに終了し、他のイベントハンドラの後に呼び出されますので、追加し、コールスタックから機能を実行


5
setInterval悪い考えです。キャンセルすることはないので、関数が継続的に実行され、サイトで最大CPUが使用される可能性があります。setTimeoutこの手法を使用する場合は、代わりに使用してください(これはお勧めしません)。アプリを特定のイベントのタイミングに依存させることはリスクの高いビジネスです。
ケーシー2016年

6
危険がロビンソンになります!危険!先のレースコンディション!
GoreDefex 2018年

私は最初にこのソリューションを思いつきました(まあsetTimeoutそうではありませんsetInterval)が250、タイミングが正しい順序で発生する前に、msまでそれをバンプする必要がありました。このバグはおそらく遅いデバイスにも存在し、遅延も顕著になります。私が試したところ、onMouseDownうまくいきました。タッチイベントも必要かしら。
ブレナン・チャン、

onClickを本当に使用したい場合は、間隔のようなものは悪くありません。ただし、間隔ではなく条件付きの再帰的タイムアウトが必要なので、永久に実行されません。これは、Selenium条件付き待機で使用されるパターンと同じです。
ウィルケイン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.