theme: condensed-night-purple
拖拉拽基础
2维向量类
创建一个2维向量vector2
,用来存放鼠标位置信息
typescript export class Vector2 { x: number; y: number; constructor(x = 0, y = 0) { this.x = x; this.y = y; } }
绑定事件
暂时只存两个值 x
和y
,后续再丰富内容,接下来就是在html中创建一个div
,再监听window上的鼠标事件,
```typescript const box = document.querySelector('#box')
const mouseDown = (e: MouseEvent) => { console.log('按下', e);
} const mouseUp = (e: MouseEvent) => { console.log('抬起', e); }
const mouseMove = (e: MouseEvent) => { console.log('移动', e?.target) }
window.addEventListener('mousemove', mouseMove) window.addEventListener('mousedown', mouseDown) window.addEventListener('mouseup', mouseUp) ```
移动元素的思路是 按下
,开始拖拽
,计算位置
,修改元素样式
,抬起
,结束本次拖拽, 所以在移动之前添加一个flag
,默认为false,按下时置为true,抬起时再置为false。
```typescript ...
let flag = false const mouseDown = (e: MouseEvent) => { flag = true } const mouseUp = (e: MouseEvent) => { flag = false }
const mouseMove = (e: MouseEvent) => { if (flag) { console.log(e.clientX, e.clientY); } }
...
```
绑定并修改元素
既然是要拖拽box
元素,那就把mousedown
方法挂载到box
上,鼠标在box上按下,flag
置为true
typescript ... box.onmousedown = mouseDown ...
把mouseMove
方法改造一下,获取到鼠标的x和y值,并存储为一个二维向量Vector2
box新的位置计算方法mouseposition - lastmouseposition + boxposition
mouseposition
为鼠标当前位置,lastmouseposition
为上一个位置,boxposition
为盒子位置,boxPadding
是鼠标到盒子左上角的位置
所以我们在移动时候,需要获取到这三个变量,并改造一下mouseMove
方法
```typescript ... if (flag) { // 盒子的位置 const { offsetLeft: boxX, offsetTop: boxY } = box const boxPosition = new Vector2(boxX, boxY) // 鼠标位置 const { clientX, clientY } = e; const mousePosition = new Vector2(clientX, clientY); // 鼠标与上一次记录的鼠标位置的差值 let offset = new Vector2(); offset = offset.subVectors(mousePosition, lastMousePosition); // 通过差值与盒子的位置,计算出盒子新的位置 const LT = new Vector2().addVectors(offset, boxPosition)
// 将当前鼠标位置记录一下
lastMousePosition = mousePosition.clone();
// 修改位置
box.style.left = LT.x + 'px'
box.style.top = LT.y + 'px'
... ```
扩展Vector2
以上的运算都是基于二维向量的,所以,扩展一下vector.js
新增几个方法
```typescript export class Vector2 { x: number; y: number; constructor(x = 0, y = 0) { this.x = x; this.y = y; } set(x: Vector2['x'], y: Vector2['y']) { this.x = x; this.y = y } // 拷贝 copy(v2: Vector2) { this.x = v2.x this.y = v2.y } // 克隆 clone() { return new Vector2(this.x, this.y) } // 向量相减 返回新的向量 subVectors(a: Vector2, b: Vector2) { this.x = a.x - b.x; this.y = a.y - b.y;
return this;
}
向量相加 返回新的向量
addVectors(a: Vector2, b: Vector2) {
this.x = a.x + b.x;
this.y = a.y + b.y;
return this;
}
} ```
效果
缩放元素
创建角标和关键点
html <div id="box" active="box"> <div class="point lt" active="lt"></div> <div class="point rt" active="rt"></div> <div class="point lb" active="lb"></div> <div class="point rb" active="rb"></div> </div>
active
属性是为了区分每个区块的作用
```less @pointW: 12px; @all: 100%; .point { width: @pointW; height: @pointW; background-color: aquamarine; position: absolute;
&.lt {
top: calc(0px - @pointW / 2);
left: calc(0px - @pointW / 2);
}
&.rt {
top: calc(0px - @pointW / 2);
left: calc(@all - @pointW / 2);
}
&.lb {
top: calc(@all - @pointW / 2);
left: calc(0px - @pointW / 2);
}
&.rb {
top: calc(@all - @pointW / 2);
left: calc(@all - @pointW / 2);
}
} ```
区分点击的区域
现在区块变多了,而且每个区块的动作都不一样,所以在mousedown
方法记录一下当前点击的是什么区块,
外部记录一下type
typescript let areaType = ''
mousedown
添加以下代码
```typescript ··· try { areaType = e.target.getAttribute('active') console.log(areaType);
} catch (error) { console.error(error) } ··· ```
因为有兼容性问题,我这里没做处理,直接捕获错误,项目开发,可不能这么简单粗暴。
现在按下的时候记录区块,抬起时将区块改为''空字符串
接下来要做的就是根据不同的区块,设置不同的动作,比如rb区块,就是右下角,作用是改变尺寸,lt是左上角,改变尺寸和位置
前文计算出来offset
的向量,为当前鼠标位置和上一次鼠标位置的差值,以右下角为例,boxPosition+boxSize+offset
最终得到具体尺寸,所以现在定义一个方法,接受offset参数,再根据不同的作用区域进行不同的处理
所以接下来将mouseMove
继续改造一下,将之前写的改变box位置的代码单独抽离出来
改造mouseMove方法
typescript ··· // 鼠标位置 const { clientX, clientY } = e; const mousePosition = new Vector2(clientX, clientY); // 鼠标与上一次记录的鼠标位置的差值 let offset = new Vector2(); mouse.copy(mousePosition) offset = offset.subVectors(mousePosition, lastMousePosition); changeBox(offset) // 将当前鼠标位置记录一下 lastMousePosition = mouse.clone(); ···
changeBox
方法是根据不同的areaType
做的不同的动作
typescript const changeBox = (offset: Vector2) => { // 盒子的位置 const { offsetLeft: boxX, offsetTop: boxY, clientWidth, clientHeight } = box const boxPosition = new Vector2(boxX, boxY) const boxSize = new Vector2(clientWidth, clientHeight) const styleInfo: BoxStyleInfo = {} if (areaType === 'box') { // 通过差值与盒子的位置,计算出盒子新的位置 const LT = new Vector2().addVectors(offset, boxPosition) styleInfo.left = LT.x styleInfo.top = LT.y } changeBoxStyle(styleInfo) }
changeBoxStyle
方法是修改box样式的
typescript interface BoxStyleInfo { width?: number; height?: number; left?: number; top?: number; } const changeBoxStyle = (info: BoxStyleInfo) => { if (info.left !== undefined) box.style.left = info.left + 'px' if (info.top !== undefined) box.style.top = info.top + 'px' if (info.width !== undefined) box.style.width = info.width + 'px' if (info.height !== undefined) box.style.height = info.height + 'px' }
rb动作
同样的方法,写一下rb
的动作
typescript ... else if (areaType === 'rb') { const size = new Vector2().addVectors(offset, boxSize) styleInfo.width = size.x styleInfo.height = size.y } ...
效果
lt动作
除了右下角不需要改变box的位置,其他的作用区域都需要改变位置,所以其他的区域相对比较复杂,以lt
为例 mouse
的位置就是box的位置,mouse - offset
就是盒子的尺寸,尺寸和位置需要同步修改,拖拽左上角后,右下角的尺寸是不变的
typescript ··· else if (areaType === 'lt') { const pos = mouse.clone() styleInfo.left = pos.x styleInfo.top = pos.y const size = boxSize.clone().sub(offset) styleInfo.width = size.x styleInfo.height = size.y } ···
rt动作
右上角拖拽时,改变的是top
,width
,height
,所以只对这几个属性进行改变即可
```typescript const top = boxPosition.y + offset.y styleInfo.top = top
const width = boxSize.x + offset.x styleInfo.width = width
const height = boxSize.y - offset.y styleInfo.height = height ```
lb动作
左下角改变的是left
, height
, width
```typescript const height = boxSize.y + offset.y styleInfo.height = height
const width = boxSize.x - offset.x styleInfo.width = width
styleInfo.left = mouse.x ```
效果
改变手势
在areaType
改变的时候 调用一下changeCursor
方法就可以,或者直接写一个监听函数
```typescript enum MouseType { NWSE = 'nwse-resize', NESW = 'nesw-resize', EW = 'ew-resize', NS = 'ns-resize', MOVE = 'move', DEFAULT = 'auto', }
const MouseTypeDisplay = { 'lt': MouseType.NWSE, 'lb': MouseType.NESW, 'rt': MouseType.NESW, 'rb': MouseType.NWSE, 'box': MouseType.MOVE, default: MouseType.DEFAULT, };
const changeCursor = () => { const cursor = MouseTypeDisplay[areaType || 'default']; document.body.style.cursor = cursor; } ```
效果
实际应用中很常见,不管是div画的框还是canvas画的框,拖拽的逻辑不变,无非就是调用canvas改变rect的方法,截图,绘制有效区,都可以使用
可以搞下来代码运行一下,另:vector2 封装的方法抄的 [threejs
](https://threejs.org/docs/index.html?q=vect#api/zh/math/Vector2)
历史文章
# three.js 打造游戏小场景(拾取武器、领取任务、刷怪)