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

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

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

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

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

     


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

    github: https://github.com/willian12345


     

     

  • 相关阅读:
    RDS - 远程桌面服务
    宝塔面板+阿里云部署springboot+vue项目
    从零开始:使用ChatGPT快速创作引人入胜的博客内容
    【Godot引擎开发】简单基础,外加一个小游戏DEMO
    Bioinformatics2022 | AdvProp+:基于集成网络的分子性质预测与药物研发
    Docker guide
    hive sql—开窗函数—累积求和和滑动求和
    MODBUS-TCP转MODBUS-RTU通信应用(S7-1200和串口服务器通信)
    Ajax,json
    VIT(Vision Transformer)学习-模型理解(一)
  • 原文地址:https://www.cnblogs.com/willian/p/16295237.html