Javascript基础之鼠标拖拉拽


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; } }

绑定事件

暂时只存两个值 xy,后续再丰富内容,接下来就是在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;
}

} ```

效果

2023-05-24 16.02.35.gif

缩放元素

创建角标和关键点

image.png

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 } ...

效果

2023-05-24 17.27.21.gif

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 } ···

lt.gif

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 ```

效果

2023-05-24 18.11.37.gif

改变手势

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; } ```

效果

2023-05-24 18.25.45.gif

实际应用中很常见,不管是div画的框还是canvas画的框,拖拽的逻辑不变,无非就是调用canvas改变rect的方法,截图,绘制有效区,都可以使用

代码地址

可以搞下来代码运行一下,另:vector2 封装的方法抄的 [threejs](https://threejs.org/docs/index.html?q=vect#api/zh/math/Vector2)

历史文章

# Javascript基础之写一个好玩的点击效果

# Javascript基础之鼠标拖拉拽

# three.js 打造游戏小场景(拾取武器、领取任务、刷怪)

# threejs 打造 world.ipanda.com 同款3D首页

# three.js——物理引擎

# three.js——镜头跟踪

# threejs 笔记 03 —— 轨道控制器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙华鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值