はじめに
ドラッグで移動、拡大縮小ができるモーダルを作ってみました。
早速完成形からお見せします。
移動
まずは、ヘッダー部分をドラッグすることでモーダルが移動ができるようにします。
1. テンプレート
<template>
<div
class="draggable-modal"
:style="{
width: `${modalWidth}px`,
height: `${modalHeight}px`,
transform: `translate(${pos.x}px, ${pos.y}px)`,
}"
>
<div
class="header"
@mousedown="
(e) => {
adjustDragStart(e)
}
"
>
header
</div>
<div class="body">body</div>
</div>
</template>
モーダルの大枠の中にヘッダーとボディーを用意します。
モーダルの大きさはmodalWidth
とmodalHeight
、位置はpos.x
とpos.y
で管理していきます。
style属性にあるtranslate(x, y)
を指定することで元に位置からX軸方向にx、Y軸方向にy移動させることができます。
ヘッダー部分にmousedownイベントを用意し、ドラッグの開始時にadjustDragStart()
を発火させます。
2. SCSS
.meeting-draggable-parts {
position: absolute;
left: 0;
top: 0;
> .header {
height: 20%;
background-color: red;
text-align: center;
}
> .body {
height: 80%;
background-color: blue;
text-align: center;
}
}
3. プロパティ
// 初期値
private initialWidth = 300
// 初期高さ
private initialHeight = 400
// 幅の最低値
private minWidth = 100
// 高さの最低値
private minHeight = 100
// モーダルの幅
private modalWidth = this.initialWidth
// モーダルの高さ
private modalHeight = this.initialHeight
// スタート時のポインターのX座標
private dragStartX = 0
// スタート時のポインターのY座標
private dragStartY = 0
// モーダルの左上隅の座標(中央に配置している)
private pos = {
x: (window.innerWidth - this.initialWidth) / 2,
y: (window.innerHeight - this.initialHeight) / 2,
}
// ドラッグスタート時の座標と大きさ
private startModalRect = {
x: 0,
y: 0,
width: 0,
height: 0,
}
// モーダル位置調整中フラグフラグ
private isAdjusting = false
4. mounted()
,beforeDestory()
に以下を記述
/**
* ドラッグ監視
*/
mounted() {
document.addEventListener('mousemove', (e) => this.onDrag(e))
document.addEventListener('mouseup', () => this.dragEnd())
}
/**
* 監視終了
*/
beforeDestroy() {
document.removeEventListener('mousemove', this.onDrag)
document.removeEventListener('mouseup', this.dragEnd)
}
onMounted
内でaddEventListener
を記述し、マウスが動いた時とマウスのドラッグ状態を解除した時を監視します。
beforeDestroy
で監視を終了させます。
5. ヘッダーをクリックした際に発火されるメソッド記述
/**
* @desc 位置調整ドラッグ開始時の処理
* @param {MouseEvent} event
*/
private adjustDragStart(event: MouseEvent) {
event.preventDefault()
event.stopPropagation()
// モーダル位置調整中フラグをtrueにする
this.isAdjusting = true
// 初期位置代入
this.dragStartX = event.clientX
this.dragStartY = event.clientY
this.startModalRect.x = this.pos.x
this.startModalRect.y = this.pos.y
this.startModalRect.width = this.modalWidth
this.startModalRect.height = this.modalHeight
}
this.isAdjusting = true
でフラグをたてます。これは後述するモーダルの大きさ調整と区別する際に使われます。
dragStart
にはクリックしたマウスの座標、startModalRect
には現在のモーダルの座標と大きさを挿入します。
6. モーダルをドラッグしている時に発火されるメソッド記述
/**
* @desc 位置調整ドラッグ中の処理
* @param {MouseEvent} event
*/
private onDrag(event: MouseEvent) {
if (!this.dragStartX || !this.dragStartY) return
// 位置移動処理
if (this.isAdjusting) {
this.pos.x = this.keepThreshold(
this.startModalRect.x + (event.clientX - this.dragStartX),
0,
window.innerWidth - this.modalWidth
)
this.pos.y = this.keepThreshold(
this.startModalRect.y + (event.clientY - this.dragStartY),
0,
window.innerHeight - this.modalHeight
)
}
}
こちらでモーダルの位置を移動させる処理を記述しています。
モーダルのX座標であるpos.x
はthis.startModalRect.x + (event.clientX - this.dragStartX)(モーダルのドラッグ開始時のモーダルのX座標 - (マウスのX座標 - ドラッグ開始時のマウスのX座標))
で計算することができます。
Y座標も同様です。
keepThreshold()
は第一引数で指定した値が、第二・第三引数で指定する最小値と最大値を下回るor上回らないようにするメソッドです。
これにより、モーダルが画面外にはみ出さないようにしています。
/**
* @desc 閾値に値を保つ処理
* @param {number | null} value
* @param {number | null} min
* @return {number}
*/
private keepThreshold(
value: number,
min: number | null,
max: number | null
): number {
if (min !== null && max === null) return value >= min ? value : min
if (min === null && max !== null) return value <= max ? value : max
if (min !== null && max !== null) {
if (value <= min) return min
if (value >= max) return max
}
return value
}
7. ドラッグ終了時の処理を記述
/**
* ドラッグ終了時の処理
*/
private dragEnd() {
this.isAdjusting = false
this.dragStartX = 0
this.dragStartY = 0
this.startModalRect.x = 0
this.startModalRect.y = 0
this.startModalRect.width = 0
this.startModalRect.height = 0
}
特に難しいことはなく、各種値を初期値に戻しているだけです。
これで、モーダルの位置をドラッグ&ドロップで調整可能になります。
モーダルサイズの拡大・縮小
次に、モーダルの四隅と四辺をドラッグすることで、モーダルサイズが拡大・縮小できるようにします。
1. 四隅と四辺に要素を追加する
class="draggable-modal"
を持つ要素の直下に以下の要素を追加します。
<span class="top" @mousedown="(e) => {resizeDragStart(e, null, 'top') }"></span>
<span class="bottom" @mousedown="(e) => {resizeDragStart(e, null, 'bottom') }"></span>
<span class="left" @mousedown="(e) => {resizeDragStart(e, 'left', null) }"></span>
<span class="right" @mousedown="(e) => {resizeDragStart(e, 'right', null) }"></span>
<span class="tleft" @mousedown="(e) => {resizeDragStart(e, 'left', 'top') }"></span>
<span class="tright" @mousedown="(e) => {resizeDragStart(e, 'right', 'top') }"></span>
<span class="bleft" @mousedown="(e) => {resizeDragStart(e, 'left', 'bottom') }"></span>
<span class="bright" @mousedown="(e) => {resizeDragStart(e, 'right', 'bottom') }"></span>
四隅と四辺に要素を配置します。ここをマウスでクリックした時に、後述するresizeDragStart()
が発火するように@mousedown
を使用しています。resizeDragStart
の引数によって、どこを起点としてサイズを変更するのかを区別しています。
2. スタイルを追加します。
各要素をそれぞれ四隅四辺に配置します。
> .top {
position: absolute;
top: 0;
right: 5px;
left: 5px;
height: 5px;
cursor: ns-resize;
}
> .bottom {
position: absolute;
bottom: 0;
right: 5px;
left: 5px;
height: 5px;
cursor: ns-resize;
}
> .left {
position: absolute;
top: 5px;
bottom: 5px;
left: 0;
width: 5px;
cursor: ew-resize;
}
> .right {
position: absolute;
top: 5px;
right: 0;
bottom: 5px;
width: 5px;
cursor: ew-resize;
}
> .tleft {
position: absolute;
top: 0;
left: 0;
width: 5px;
height: 5px;
cursor: nwse-resize;
}
> .tright {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 5px;
cursor: nesw-resize;
}
> .bleft {
position: absolute;
bottom: 0;
left: 0;
width: 5px;
height: 5px;
cursor: nesw-resize;
}
> .bright {
position: absolute;
right: 0;
bottom: 0;
width: 5px;
height: 5px;
cursor: nwse-resize;
}
tleft
の「t」はtop
、bleft
の「b」はbottom
を表しています。
3. 四隅四辺がクリックされた時の処理を記述
/**
* @desc リサイズ開始時の処理
* @param {MouseEvent} event
* @param {'left'|'right'|null} horizon
* @param {'top'|'bottom'|null} vertical
*/
private resizeDragStart(
event: MouseEvent,
horizon: 'left' | 'right' | null,
vertical: 'top' | 'bottom' | null
) {
event.preventDefault()
event.stopPropagation()
this.resizeDragHorizon = horizon
this.resizeDragVertical = vertical
this.dragStartX = event.clientX
this.dragStartY = event.clientY
this.startModalRect.x = this.pos.x
this.startModalRect.y = this.pos.y
this.startModalRect.width = this.modalWidth
this.startModalRect.height = this.modalHeight
}
前述したモーダルの位置調整と同じく、モーダルの初期位置や大きさ、クリックした時のマウスの座標をそれぞれ入力する他、
resizeDragHorizon
とresizeDragVertical
に引数で渡した値を代入します。
4. onDrag()に以下の記述を追加する
// topのリサイズの処理
if (this.resizeDragVertical === 'top') {
const difference = event.clientY - this.dragStartY
if (this.modalHeight <= this.minHeight && difference > 0) return
const bottomY = this.startModalRect.y + this.startModalRect.height
this.pos.y = this.keepThreshold(
this.startModalRect.y + difference,
0,
bottomY - this.minHeight
)
this.modalHeight = bottomY - this.pos.y
}
// bottomのリサイズ処理
if (this.resizeDragVertical === 'bottom') {
const difference = event.clientY - this.dragStartY
this.modalHeight = this.keepThreshold(
this.startModalRect.height + difference,
this.minHeight,
window.innerHeight - this.startModalRect.y
)
}
// leftのリサイズ処理
if (this.resizeDragHorizon === 'left') {
const difference = event.clientX - this.dragStartX
if (this.modalWidth <= this.minWidth && difference > 0) return
const rightX = this.startModalRect.x + this.startModalRect.width
this.pos.x = this.keepThreshold(
this.startModalRect.x + difference,
0,
rightX - this.minWidth
)
this.modalWidth = rightX - this.pos.x
}
// rightのリサイズ処理
if (this.resizeDragHorizon === 'right') {
const difference = event.clientX - this.dragStartX
this.modalWidth = this.keepThreshold(
this.startModalRect.width + difference,
this.minWidth,
window.innerWidth - this.startModalRect.x
)
}
}
どこを起点にするかによって処理が変わっていきいます。四隅の場合は、垂直方向と水平方向の二つの処理が必要となります。
resizeDragHorizon = 'top'
の時、すなわちモーダルの上からサイズを調整する場合を考えてみましょう。
このとき、変化するのはモーダルのY座標と、モーダルの高さです。先述したモーダルの移動の時と同じ計算式で算出されます。この時の最大値(モーダルの最低な座標)は、モーダルの可変の座標から、minHeightを引いた値にすることで、高さが最低かつ、一番低い時のY座標になります。
モーダルの高さは、ドラッグを開始した時のモーダルの下辺のY座標から、先ほどの計算したモーダルのY座標を引くことで算出されます。
次に、resizeDragHorizon = 'bottom'
の時、すなわちモーダルの下かサイズを調整する場合です。
この場合のpos.y
は下からのサイズ調整の場合は変化しません。
modalHeight
は、this.startModalRext.height + event.clientY - this.dragStartY(ドラッグ開始時のモーダルの高さ + マウスのY座標 - ドラッグ開始時のマウスのY座標)
で取得することができます。
また、この時の最大幅を,window.innerHeight - this.startModalRect.y
にすることで、ウィンドウの一番下までが限界の高さにすることができます。
水平方向からの処理に関しても同じ考えで計算することができます。
以上でドラッグ&ドロップで移動、サイズが調整できるようになります。