如何实现蓝色小圆可拖动,并且边界限制在灰色大圆内?如下所示

需求源自 业务上遇到一个组件需求,设计师设计了一个“脸型整合器”根据可拖动小圆的位置与其它脸型的位置关系计算融合比例

如图

我们先把具体的人脸功能去掉再分析

中间的蓝色小圆可由鼠标拖动,但拖动需要限制在大的圆形内

以下代码全部为 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>

至于其它计算距离,计算反比例的脸形整合业务代码,就不放了

脸形容器的位置还是用圆形公式算出来

各个脸形容器与小圆点距离,距离还是用距离公式得出

知道各个距离了就可以根据业务需要算比例了


转载入注明博客园池中物 willian12345@126.com sheldon.wang

github: https://github.com/willian12345


最新文章

  1. MSDB数据库置疑的解决方法
  2. 让Java和MySQL连接起来
  3. UITableView使用
  4. AsyncTask的基础讲解
  5. Mono for Android (3)-- AbsoluteLayout、FrameLayout、LinearLayout、RelativeLayout、TableLayout
  6. 自动匹配HTTP请求中对应实体参数名的数据(性能不是最优)
  7. BootStrap 模态框禁用空白处点击关闭,手动显示隐藏,垂直居中
  8. iOS 监听键盘变化
  9. chromium blog
  10. webstorm入手笔记
  11. JS中的循环嵌套 BOM函数
  12. hadoop2.8和spark2.1完全分布式搭建
  13. 《安卓网络编程》之第三篇 使用Apache接口
  14. 【charger battery 充電 充電器 電池】過充保護警告訊息 over charging protection,Battery over voltage protection, warning message
  15. AWS Tagging Strategies
  16. H5 文字属性
  17. PC端的鼠标拖拽滑动
  18. Promise 原理
  19. Linux 重启网卡失败 Job for network.service failed because the control process exited with error code. See &quot;systemctl status network.service&quot; and &quot;journalctl -xe&quot; for details.
  20. poj1470 LCA倍增法

热门文章

  1. Canvas 核心技术
  2. C#ASP.NET网站开发步骤
  3. java中String类的用法
  4. No origin bean specified和 No destination bean specified
  5. js 生成 pdf 文件
  6. 记一次使用git报错,解决Unable to negotiate with **** port 22: no matching host key type found. Their offer: ssh-rsa
  7. Prometheus+Grafana安装搭建
  8. 安卓记账本开发学习day10
  9. OpenHarmony 3.1 Beta版本关键特性解析——HiStreamer框架大揭秘
  10. C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解