前言
迫于很久没更新博客,水一篇小文章,最终效果:
是一个双向绑定 Input 框的旋转按钮,旋转按钮的图片可以根据业务场景替换,例如换成音量键之类的。
基本思路
- 双向绑定:
v-model
实现
- 鼠标点击时记录抓点位置,允许 move 事件回调
- move 事件回调中旋转图片
- 鼠标抬起时停止 move 事件回调
代码实现
模板代码
模板代码需要注意的地方是这个按钮分为内外两圈,里面那圈是不需要跟着转的,所以ref
要正确给到外面那圈的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<template>
<div
class="compass-wrap"
@mousedown="recordStartPoint"
@mousemove="handleMouseMove"
@mouseup="stopCompute"
@mouseleave="stopCompute"
>
<div class="main-borad" ref="boardRef">
<span class="n-text">N</span>
<div class="borad"></div>
</div>
<div class="inner"></div>
</div>
</template>
|
逻辑代码
组件初始化时,获取元素的圆心坐标,用于后续的计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<script setup>
import { ref, onMounted, nextTick } from 'vue'
const props = defineProps({
value: {
type: Number,
default: 0
}
})
const emit = defineEmits(['update:value'])
// dom ref
const boardRef = ref(null)
// 圆心坐标
let centerPoint = { x: 0, y: 0 }
// 获取圆心坐标
function getCenter() {
const { x, y } = boardRef.value.getBoundingClientRect()
const width = boardRef.value.offsetWidth
const height = boardRef.value.offsetHeight
centerPoint.x = x + width / 2
centerPoint.y = y + height / 2
}
// 在 onMounted 调用
onMounted(() => {
nextTick(() => {
getCenter()
})
})
</script>
|
recordStartPoint
鼠标点击回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<script setup>
let startAngle = 0 // 鼠标落点角度
let isDragging = false // 是否开启 move 事件
function recordStartPoint(e) {
startAngle = computeAngle(e)
isDragging = true
}
/**
* 复用函数:计算起始角度 & 旋转角度
* @param e 鼠标事件
*/
function computeAngle(e) {
// 鼠标坐标与圆心的弧度转角度
const radian = Math.atan((e.clientY - centerPoint.y) / (e.clientX - centerPoint.x))
let angle = (radian * 180) / Math.PI
if (e.clientX < centerPoint.x) {
angle += 180
}
return angle
}
</script>
|
handleMouseMove
鼠标移动回调计算旋转角度:
1
2
3
4
5
6
7
8
9
10
11
|
<script setup>
let renderAngle = 0 // 实际用于渲染的旋转角度
const displayAngle = ref(0) // css v-bind 值
function handleMouseMove(e) {
if (!isDragging) return
const movingAngle = Math.round(startOffsetAngle + computeAngle(e) - startAngle)
renderAngle = movingAngle < 0 ? (movingAngle + 360) % 360 : movingAngle % 360
emit('update:value', renderAngle + '')
displayAngle.value = renderAngle + 'deg'
}
</script>
|
stopCompute
鼠标抬起时回调,并停止计算指针移动
1
2
3
4
5
6
7
|
<script setup>
let startOffsetAngle = 0 // 记录指南针表盘当前的偏移角度
function stopCompute() {
isDragging = false
startOffsetAngle = renderAngle
}
</script>
|
暴露手动设置罗盘方向的方法给组件外部使用:
1
2
3
4
5
6
7
8
9
10
11
|
<script setup>
// 手动设置罗盘角度
function setCompassRotate(val) {
startOffsetAngle = val
displayAngle.value = val + 'deg'
}
defineExpose({
setCompassRotate
})
</script>
|
增加初始化时设置罗盘方向的逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
|
<script setup>
import { ref, onMounted, nextTick } from 'vue'
onMounted(() => {
nextTick(() => {
getCenter()
// 新增
if (props.value !== 0) {
setCompassRotate(props.value)
}
})
})
</script>
|
CSS v-bind 绑定
将刚刚记录的旋转角度值直接通过 v-bind
绑定到 css 中即可
1
2
3
4
5
|
<style lang="less" scoped>
.main-borad {
transform: rotate(v-bind(displayAngle));
}
</style>
|
(完)