如何实现将拖动物体限制在某个圆形内--实现方式vue3.0

简介: 如何实现将拖动物体限制在某个圆形内--实现方式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>

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

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

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

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

相关文章
|
6月前
|
JavaScript API
Vue使用vue-3d-model组件预览3D三维文件、立体文件,支持旋转、自动播放
Vue使用vue-3d-model组件预览3D三维文件、立体文件,支持旋转、自动播放
|
27天前
|
JavaScript
|
3月前
|
JavaScript
基于Vue2或Vue3实现任意上下左右拖拽悬浮的元素,且配置为自定义的全局指令
这篇文章介绍了如何在Vue 2或Vue 3项目中实现一个自定义的全局指令`v-dragSwitch`,用于创建可以任意方向拖拽并悬浮的元素,同时包含边界处理的逻辑。
916 2
基于Vue2或Vue3实现任意上下左右拖拽悬浮的元素,且配置为自定义的全局指令
|
2月前
|
Web App开发 存储 前端开发
vue2精简方式实现鼠标在方框内拖拽效果源码
vue2精简方式实现鼠标在方框内拖拽效果源码
55 3
|
4月前
|
存储 容器
vue3【实战】来回拖拽放置图片
vue3【实战】来回拖拽放置图片
115 2
|
4月前
|
JavaScript
vue实战——元素的拖拽 + 控制元素无法拖拽出盒子 + 随元素拖拽自适应变化大小的盒子
vue实战——元素的拖拽 + 控制元素无法拖拽出盒子 + 随元素拖拽自适应变化大小的盒子
37 1
|
4月前
|
JavaScript 前端开发
【vue】 vue2 canvas实现在图片上选点,画区域并将 坐标传递给后端
【vue】 vue2 canvas实现在图片上选点,画区域并将 坐标传递给后端
127 0
|
4月前
|
JavaScript
vue 组件封装 -- 添加【呼吸】动画效果(两种: 淡入>>淡入,淡入>>淡出>>淡入)
vue 组件封装 -- 添加【呼吸】动画效果(两种: 淡入>>淡入,淡入>>淡出>>淡入)
31 0
|
4月前
|
JavaScript
vue 半场动画【实战范例】购物时小球弧线飞落
vue 半场动画【实战范例】购物时小球弧线飞落
27 0
|
6月前
|
JavaScript 定位技术 API
OpenLayers入门-第二篇、在vue3中使用elementplus制作图层控件,图层切换,显示隐藏,图层排序
OpenLayers入门-第二篇、在vue3中使用elementplus制作图层控件,图层切换,显示隐藏,图层排序
128 1