新Edgeでクリック時にmousemoveイベントが発火される問題
こんにちわ。山崎です。最近更新できておらず、久々の投稿になります。 今回はとあるプロジェクトの開発中に2020年1月15日にリリースされた新Egdeでの不具合を見つけたのでそれについて書いていこうと思います。
何を作っていたのか
イベントサイトの会場マップでそのマップをドラッグしたらマップの位置を移動し、 クリックしたら該当の小間番号のモーダルが開き詳細が見られるというものです。 実際のソースコードを載せることはできないので、今回複数の企業が合同セミナーイベントを行うという程でかなり簡易的なものですがdemoを作ってみました。
デモはこちら
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>EdgeとChromeでclick時にmousemoveが発火してしまう問題</title>
<style>
html, body {
box-sizing: border-box;
padding: 0;
margin: 0;
height: 100%;
}
#wrapper {
position: relative;
height: 100%;
}
#map {
display: flex;
flex-wrap: wrap;
position: absolute;
top: calc(50% - 100px);
left: calc(50% - 100px);
background: #666666;
width: 200px;
height: 200px;
text-align: center;
color: #fff;
cursor: pointer;
}
#map > div {
display: flex;
justify-content: center;
align-items: center;
width: calc(50% - 2px);
height: calc(50% - 2px);
}
#map > div:nth-child(even) {
border-left: 2px solid #fff;
}
#map > div:nth-child(n+3) {
border-top: 2px solid #fff;
}
#overlay {
display: none;
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .7);
}
#modal {
display: none;
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
width: 600px;
height: 800px;
color: #666666;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="map">
<div>企業A</div>
<div>企業B</div>
<div>企業C</div>
<div>企業D</div>
</div>
<div id="modal"></div>
<div id="overlay"></div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.js"
integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"
integrity="sha256-0YPKAwZP7Mp3ALMRVB2i8GXeEndvCq3eSl/WsAl1Ryk="
crossorigin="anonymous"></script>
<script>
$(function () {
$('#map').draggable();
var isDragged = false;
$('#map > div').mousedown(function () {
isDragged = false;
}).mousemove(function (event) {
isDragged = true;
}).mouseup(function () {
if (!isDragged) {
$('#modal').text($(this).text()).show();
$('#overlay').show();
}
});
$('#overlay').click(function () {
$('#overlay').hide();
$('#modal').hide();
});
});
</script>
</body>
</html>
jQueryの部分だけ抜粋して説明します。
まず#map
にjQueryUIのdraggable
メソッドを適用してドラッグ可能にします。
$('#map').draggable();
そして#map
の中の各企業のブースをクリックするとモーダルとオーバーレイが開くようにします。
一見↓でいいような気もしますが、これだと地図をドラッグして離した時にもクリックイベントが発火されてしまうため、モーダルが開いてしまいます。
$('#map > div').click(function () {
$('#overlay').show();
$('#modal').show();
});
下記のようにmousedown
とmouseup
メソッドを使い分けることでマウスを押した時、離した時でそれぞれイベントを発火させることができます。
$(function () {
var isDragged = false;
$('#map > div').mousedown(function () {
isDragged = false;
}).mousemove(function () {
isDragged = true;
}).mouseup(function () {
if (!isDragged) {
$('#modal').text($(this).text()).show();
$('#overlay').show();
}
});
});
まずグローバル変数のisDragged
を定義して初期値をfalse
に指定します。
var isDragged = false;
mousedown
イベントが発火されたら、isDraggedをfalse
に指定します。
そのあとマウスを離すまでの間にカーソル位置が変わった場合、
mousemove
イベントが発火されるのでisDragged
をtrue
に指定します。
$('#map > div').mousedown(function () {
isDragged = false;
}).mousemove(function () {
isDragged = true;
})
最後にmouseup
イベントが発火されたタイミングでisDragged
がfalse
だった場合、
つまり、マウスを押してから離すまでの間にカーソル位置が変わらなかった場合、モーダルを表示させるようにします。
省略.mouseup(function () {
if (!isDragged) {
$('#modal').text($(this).text()).show();
$('#overlay').show();
}
}
(厳密に言うとmousemove
イベントは要素内でマウスが動く度に発火するため、要素内でカーソルを動かしていた場合常にisDragged
はtrue
になるため、
mousedown
イベントを使用してマウスが押されたら、false
になるようにしています。)
※demoはこちら↓
このソースコードで何が問題だったのか
自分は普段Macを使用しているため、このようなソースコードでも問題なく動いたのですが、
ある時に「Windowsの新Edgeでモーダルが開かない」という報告があり、調査したところ、
マウスが押された後、離すまでの間にカーソル位置を動かしてないのにmousemove
イベントが発火されていてisDragged
がtrue
になり、ドラッグされたことになっているということがわかりました。
また、2020年1月15日にリリースされたEdgeはChromeベースで作られているため、Google Chromeでも同様の現象が見られました。
解決した方法
マウスを押した時のカーソルのx位置・y位置と離した時のx位置・y位置を比較して数値が違った場合は
isDragged
をtrue
にし、同じだったらisDragged
をfalse
にするという実装方法にしたら 無事うまくいきましした。
以下がjQueryを書き換えたコードになります。
$(function () {
$('#map').draggable();
var isDragged = false;
var screenX = 0;
var screenY = 0;
$('#map > div').mousedown(function (event) {
isDragged = false;
screenX = event.screenX;
screenY = event.screenY;
}).mousemove(function (event) {
var screenXOld = screenX;
var screenYOld = screenY;
screenX = event.screenX;
screenY = event.screenY;
if (screenX !== screenXOld || screenY !== screenYOld) {
isDragged = true;
}
}).mouseup(function () {
if (!isDragged) {
$('#modal').text($(this).text()).show();
$('#overlay').show();
}
});
$('#overlay').click(function () {
$('#overlay').hide();
$('#modal').hide();
});
});
まず、カーソルのx位置y位置の数値をいれるグローバル変数を2つ定義し、初期値を0に指定します。
var offsetX = 0;
var offsetY = 0;
mousedown
イベントが発火されたら、その時点のx位置・y位置を変数に格納します。
カーソル位置はEvent
オブジェクトのscreenX
、screenY
メソッドで取得することができます。
offsetX = event.offsetX;
offsetY = event.offsetY;
そして、mousemove
イベントで先ほど定義したカーソル位置を別の変数に退避し、screenX
とscreenY
を更新します。
最後にx位置y位置をそれぞれ比較してどちらかが違った場合、ドラッグされたということでisDragged
をtrue
にします。
var screenXOld = screenX;
var screenYOld = screenY;
screenX = event.screenX;
screenY = event.screenY;
if (screenX !== screenXOld || screenY !== screenYOld) {
isDragged = true;
}
この実装方法で新EdgeやWidows版Chromeでも問題なく動作するようになりました。 同じ要素でドラッグとクリックのイベントを両方定義することはよくあると思うので参考になれば幸いです。
ネタ不足で次回はいつになるかわかりませんが、最近書けてなかったので取り戻すように頑張って更新しようと思います。