新Edgeでクリック時にmousemoveイベントが発火される問題

新Edgeでクリック時にmousemoveイベントが発火される問題

こんにちわ。山崎です。最近更新できておらず、久々の投稿になります。 今回はとあるプロジェクトの開発中に2020年1月15日にリリースされた新Egdeでの不具合を見つけたのでそれについて書いていこうと思います。

何を作っていたのか

イベントサイトの会場マップでそのマップをドラッグしたらマップの位置を移動し、 クリックしたら該当の小間番号のモーダルが開き詳細が見られるというものです。 実際のソースコードを載せることはできないので、今回複数の企業が合同セミナーイベントを行うという程でかなり簡易的なものですがdemoを作ってみました。

デモはこちら

画面収録 2020-06-29 16.35.25.mov

ソースコード

<!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();
});

下記のようにmousedownmouseupメソッドを使い分けることでマウスを押した時、離した時でそれぞれイベントを発火させることができます。

$(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イベントが発火されるのでisDraggedtrueに指定します。

$('#map > div').mousedown(function () {
    isDragged = false;
}).mousemove(function () {
    isDragged = true;
})

最後にmouseupイベントが発火されたタイミングでisDraggedfalseだった場合、 つまり、マウスを押してから離すまでの間にカーソル位置が変わらなかった場合、モーダルを表示させるようにします。

省略.mouseup(function () {
    if (!isDragged) {
        $('#modal').text($(this).text()).show();
        $('#overlay').show();
    }
}

(厳密に言うとmousemoveイベントは要素内でマウスが動く度に発火するため、要素内でカーソルを動かしていた場合常にisDraggedtrueになるため、 mousedownイベントを使用してマウスが押されたら、falseになるようにしています。)

※demoはこちら↓

このソースコードで何が問題だったのか

自分は普段Macを使用しているため、このようなソースコードでも問題なく動いたのですが、 ある時に「Windowsの新Edgeでモーダルが開かない」という報告があり、調査したところ、 マウスが押された後、離すまでの間にカーソル位置を動かしてないのにmousemoveイベントが発火されていてisDraggedtrueになり、ドラッグされたことになっているということがわかりました。 また、2020年1月15日にリリースされたEdgeはChromeベースで作られているため、Google Chromeでも同様の現象が見られました。

解決した方法

マウスを押した時のカーソルのx位置・y位置と離した時のx位置・y位置を比較して数値が違った場合は isDraggedtrueにし、同じだったらisDraggedfalseにするという実装方法にしたら 無事うまくいきましした。 以下が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オブジェクトのscreenXscreenYメソッドで取得することができます。

offsetX = event.offsetX;
offsetY = event.offsetY;

そして、mousemoveイベントで先ほど定義したカーソル位置を別の変数に退避し、screenXscreenYを更新します。 最後にx位置y位置をそれぞれ比較してどちらかが違った場合、ドラッグされたということでisDraggedtrueにします。

var screenXOld = screenX;
var screenYOld = screenY;
screenX = event.screenX;
screenY = event.screenY;
if (screenX !== screenXOld || screenY !== screenYOld) {
    isDragged = true;
}

この実装方法で新EdgeやWidows版Chromeでも問題なく動作するようになりました。 同じ要素でドラッグとクリックのイベントを両方定義することはよくあると思うので参考になれば幸いです。

ネタ不足で次回はいつになるかわかりませんが、最近書けてなかったので取り戻すように頑張って更新しようと思います。

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ