概要:マウス位置とドラッグ対象要素の相対位置を計算し、その差分を使って移動後の座標を決定します。
サンプルコード:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ドラッグ機能の基本</title>
<style>
#draggable {
width: 100px;
height: 100px;
background-color: blue;
position: absolute;
}
</style>
</head>
<body>
<div id="draggable"></div>
<script>
window.addEventListener('DOMContentLoaded', () => {
const target = document.getElementById('draggable');
let offsetX = 0;
let offsetY = 0;
target.addEventListener('mousedown', (e) => {
offsetX = e.clientX - target.getBoundingClientRect().left;
offsetY = e.clientY - target.getBoundingClientRect().top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', onMouseMove);
});
function onMouseMove(e) {
target.style.left = `${e.clientX - offsetX}px`;
target.style.top = `${e.clientY - offsetY}px`;
}
return false;
});
});
</script>
</body>
</html>
上記のコードでは、単純なDIV要素に対して問題なく動作しますが、ページ内のテキストを選択した後に要素をドラッグすると、ブラウザのデフォルトのテキスト選択動作が発生して意図しない動作になります。
この問題は、マウスダウン時にテキストが選択されている場合、ブラウザがデフォルトでテキストのドラッグ動作をトリガーするためです。
解決策: デフォルト動作を抑止するには return false を使用します。また、Internet Explorer では非標準の setCapture() を使用してグローバルイベントキャプチャを設定することも可能です。
setCapture() の互換性:
- IE: 対応しており効果があります。
- Firefox: 対応していますが効果はありません。
- Chrome: 非対応です。
改良版のコード例:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>改良版ドラッグ機能</title>
<style>
#draggable {
width: 100px;
height: 100px;
background-color: green;
position: absolute;
}
</style>
</head>
<body>
<p>
問題: ページ内に選択されたテキストがある場合、ブラウザのデフォルトのテキストドラッグ動作が発生します。<br>
解決策: `return false` を使用してデフォルト動作を抑制します。
</p>
<div id="draggable"></div>
<script>
window.addEventListener('DOMContentLoaded', () => {
const target = document.getElementById('draggable');
let offsetX = 0;
let offsetY = 0;
target.addEventListener('mousedown', (e) => {
if (target.setCapture) {
target.setCapture();
}
offsetX = e.clientX - target.getBoundingClientRect().left;
offsetY = e.clientY - target.getBoundingClientRect().top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', onMouseMove);
if (target.releaseCapture) {
target.releaseCapture();
}
});
function onMouseMove(e) {
target.style.left = `${e.clientX - offsetX}px`;
target.style.top = `${e.clientY - offsetY}px`;
}
return false;
});
});
</script>
</body>
</html>
さらに、以下のコードのように関数としてカプセル化することができます:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ドラッグ機能のカプセル化</title>
<style>
#draggable {
width: 100px;
height: 100px;
background-color: purple;
position: absolute;
}
</style>
</head>
<body>
<p>
問題: ページ内に選択されたテキストがある場合、ブラウザのデフォルトのテキストドラッグ動作が発生します。<br>
解決策: `return false` を使用してデフォルト動作を抑制します。
</p>
<div id="draggable"></div>
<script>
function enableDrag(element) {
let offsetX = 0;
let offsetY = 0;
element.addEventListener('mousedown', (e) => {
offsetX = e.clientX - element.getBoundingClientRect().left;
offsetY = e.clientY - element.getBoundingClientRect().top;
if (element.setCapture) {
element.setCapture();
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', onMouseMove);
if (element.releaseCapture) {
element.releaseCapture();
}
});
function onMouseMove(e) {
element.style.left = `${e.clientX - offsetX}px`;
element.style.top = `${e.clientY - offsetY}px`;
}
return false;
});
}
window.addEventListener('DOMContentLoaded', () => {
const draggableElement = document.getElementById('draggable');
enableDrag(draggableElement);
});
</script>
</body>
</html>