如何實現將拖動物體限制在某個圓形內–實現方式vue3.0
如何實現藍色小圓可拖動,並且邊界限制在灰色大圓內?如下所示
需求源自 業務上遇到一個組件需求,設計師設計了一個「臉型整合器」根據可拖動小圓的位置與其它臉型的位置關係計算融合比例
如圖
我們先把具體的人臉功能去掉再分析
中間的藍色小圓可由滑鼠拖動,但拖動需要限制在大的圓形內
以下程式碼全部為 vue3.0版本,如果你是vue2.0, react,或者原生js ,實現原理都一樣
第一步 先畫出UI
我們的藍色可拖動小圓點 pointer = ref(null) 為 小圓點dom的 引用
<template>
<div class="face-blender-container">
<div class="blender-circle">
<div
class="blend-pointer"
ref="pointer"
:style="{
left: `${pointerPosition.x}px`,
top: `${pointerPosition.y}px`,
}"
></div>
</div>
</div>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from "vue";
export default {
setup() {
const BLENDER_BORDER_WIDTH = 2; // 圓形混合器邊寬
const BLENDER_RADIUS = 224 * 0.5 - BLENDER_BORDER_WIDTH; // 圓形混合器半徑
// 圓形混合器中心點
const center = {
x: BLENDER_RADIUS,
y: BLENDER_RADIUS,
};
const state = reactive({
pointerPosition: { x: center.x, y: center.y },
});
const pointer = ref(null);
return {
...toRefs(state),
pointer,
};
},
};
</script>
<style lang="less" scoped>
@stageDiameter: 360px;
@blenderCircleDiameter: 224px;
@PointerDiameter: 20px;
.face-blender-container {
position: relative;
width: @stageDiameter;
height: @stageDiameter;
}
.blender-circle {
position: absolute;
left: 50%;
top: 50%;
margin-left: @blenderCircleDiameter * -0.5;
margin-top: @blenderCircleDiameter * -0.5;
width: @blenderCircleDiameter;
height: @blenderCircleDiameter;
border-radius: @blenderCircleDiameter;
background: rgba(255, 255, 255, 0.04);
border: 2px solid rgba(255, 255, 255, 0.08);
}
.blend-pointer {
position: absolute;
left: 50%;
top: 50%;
width: @PointerDiameter;
height: @PointerDiameter;
margin-left: @PointerDiameter * -0.5;
margin-top: @PointerDiameter * -0.5;
border-radius: @PointerDiameter;
background: #11bbf5;
border: 2px solid #ffffff;
z-index: 10;
}
</style>
ui 如圖(整體背景為黑色)
第二步 實現小圓點的無限制拖動
此時小圓點是可以拖到圓外的如圖
// 可拖動圓型指示器
const initPointer = () => {
const pointerDom = pointer.value;
pointerDom.onmousedown = (e) => {
// 滑鼠按下,計算當前元素距離可視區的距離
const originX = e.clientX - pointerDom.offsetLeft - POINTER_RADIUS;
const originY = e.clientY - pointerDom.offsetTop - POINTER_RADIUS;
document.onmousemove = function (e) {
// 通過事件委託,計算移動的距離
const left = e.clientX - originX;
const top = e.clientY - originY;
state.pointerPosition.x = left
state.pointerPosition.y = top
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
};
};
onMounted(() => {
initPointer();
});
注意是在onMouted 勾子內初始化的可拖動程式碼
第三步 實現小圓點的限制在大圓內拖動
由於是要限制在圓形內,與限制在方形內的通常計算方法不一樣
關鍵點是計算滑鼠 mousemove 時與大圓中心點的弧度 radian 與 距離 dist
弧度公式
dx = x2 – x1
dy = y2 – y1
radian = Math.atan2(dy, dx)
距離公式
dist = Math.sqrt(dx * dx + dy * dy)
當計算出了弧度與距離後,則要計算具體位置了
圓形位置公式
x = 半徑 * Math.cos(弧度) + 中心點x
y = 半徑 * Math.sin(弧度) + 中心點y
可以看出在圓形公式內控制或限制半徑,就限制了小圓的可拖動最大半徑範圍,所以需要判斷,當dist距離大於等於半徑時,計算圓形公式內的半徑設置為大圓半徑即可
具體程式碼
// 計算 x y
const getPositionByRadian = (radian, radius) => {
const x = radius * Math.cos(radian) + center.x;
const y = radius * Math.sin(radian) + center.y;
return { x, y };
};
// 可拖動圓形指示器
const initPointer = () => {
const pointerDom = pointer.value;
pointerDom.onmousedown = (e) => {
// 滑鼠按下,計算當前元素距離可視區的距離
const originX = e.clientX - pointerDom.offsetLeft - POINTER_RADIUS;
const originY = e.clientY - pointerDom.offsetTop - POINTER_RADIUS;
document.onmousemove = function (e) {
// 通過事件委託,計算移動的距離
const left = e.clientX - originX;
const top = e.clientY - originY;
const dx = left - center.x;
const dy = top - center.y;
// 計算當前滑鼠與中心點的弧度
const radian = Math.atan2(dy, dx);
// 計算當前滑鼠與中心點距離
const dist = Math.sqrt(dx * dx + dy * dy);
const radius = dist >= BLENDER_RADIUS ? BLENDER_RADIUS : dist;
// 根據半徑與弧度計算 x, y
const { x, y } = getPositionByRadian(radian, radius);
state.pointerPosition.x = x
state.pointerPosition.y = y
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
};
};
這樣就實現了最大可拖動範圍限制在大圓邊界
整體程式碼
<template>
<div class="face-blender-container">
<div class="blender-circle">
<div
class="blend-pointer"
ref="pointer"
:style="{
left: `${pointerPosition.x}px`,
top: `${pointerPosition.y}px`,
}"
></div>
</div>
</div>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from "vue";
export default {
setup() {
const BLENDER_BORDER_WIDTH = 2; // 圓形混合器邊寬
const BLENDER_RADIUS = 224 * 0.5 - BLENDER_BORDER_WIDTH; // 圓形混合器半徑
const POINTER_RADIUS = 20 * 0.5; // 可拖動指示器半徑
// 圓形混合器中心點
const center = {
x: BLENDER_RADIUS,
y: BLENDER_RADIUS,
};
const state = reactive({
pointerPosition: { x: center.x, y: center.y },
});
const pointer = ref(null);
// 計算 x y
const getPositionByRadian = (radian, radius) => {
const x = radius * Math.cos(radian) + center.x;
const y = radius * Math.sin(radian) + center.y;
return { x, y };
};
// 可拖動圓型指示器
const initPointer = () => {
const pointerDom = pointer.value;
pointerDom.onmousedown = (e) => {
// 滑鼠按下,計算當前元素距離可視區的距離
const originX = e.clientX - pointerDom.offsetLeft - POINTER_RADIUS;
const originY = e.clientY - pointerDom.offsetTop - POINTER_RADIUS;
document.onmousemove = function (e) {
// 通過事件委託,計算移動的距離
const left = e.clientX - originX;
const top = e.clientY - originY;
const dx = left - center.x;
const dy = top - center.y;
// 計算當前滑鼠與中心點的弧度
const radian = Math.atan2(dy, dx);
// 計算當前滑鼠與中心點距離
const dist = Math.sqrt(dx * dx + dy * dy);
const radius = dist >= BLENDER_RADIUS ? BLENDER_RADIUS : dist;
// 根據半徑與弧度計算 x, y
const { x, y } = getPositionByRadian(radian, radius);
state.pointerPosition.x = x
state.pointerPosition.y = y
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
};
};
onMounted(() => {
initPointer();
});
return {
...toRefs(state),
pointer,
};
},
};
</script>
<style lang="less" scoped>
@stageDiameter: 360px;
@blenderCircleDiameter: 224px;
@faceCircleDiameter: 64px;
@PointerDiameter: 20px;
.face-blender-container {
position: relative;
width: @stageDiameter;
height: @stageDiameter;
}
.blender-circle {
position: absolute;
left: 50%;
top: 50%;
margin-left: @blenderCircleDiameter * -0.5;
margin-top: @blenderCircleDiameter * -0.5;
width: @blenderCircleDiameter;
height: @blenderCircleDiameter;
border-radius: @blenderCircleDiameter;
background: rgba(255, 255, 255, 0.04);
border: 2px solid rgba(255, 255, 255, 0.08);
}
.blend-pointer {
position: absolute;
left: 50%;
top: 50%;
width: @PointerDiameter;
height: @PointerDiameter;
margin-left: @PointerDiameter * -0.5;
margin-top: @PointerDiameter * -0.5;
border-radius: @PointerDiameter;
background: #11bbf5;
border: 2px solid #ffffff;
z-index: 10;
}
</style>
至於其它計算距離,計算反比例的臉形整合業務程式碼,就不放了
臉形容器的位置還是用圓形公式算出來
各個臉形容器與小圓點距離,距離還是用距離公式得出
知道各個距離了就可以根據業務需要算比例了
轉載入註明部落格園池中物 [email protected] sheldon.wang
github: //github.com/willian12345