SVG 对象在拖动时不遵守边界

问题描述 投票:0回答:1

我正在编写一些 JavaScipt 代码,旨在允许用户将 SVG 圆圈拖到许多 SVG 矩形之一的内部。一旦 SVG 圆被放置在一个矩形内,它就可以在该矩形内部周围拖动,但它不应该能够退出矩形。我的代码最初似乎尊重矩形的边界,但我仍然可以毫不费力地将它推过边界。我本来有点工作,但运动一点也不流畅。当我提高拖动的平滑度时,边界不再受到尊重。任何帮助将不胜感激。我好像在兜圈子。

    var svg = document.querySelector('svg');
    var circle = document.getElementById('myCircle');
    var circleRadius = circle.getAttribute('r');
    var rect1 = {x: 50, y: 50, width: 100, height: 100};
    var rect2 = {x: 200, y: 50, width: 100, height: 100};
    var rect3 = {x: 350, y: 50, width: 100, height: 100};
    var dragging = false;
    var offset = {x: 0, y: 0};

    // add event listeners to the circle
    svg.addEventListener('mousedown', startDrag);
    svg.addEventListener('mousemove', drag);
    svg.addEventListener('mouseup', endDrag);
    svg.addEventListener('mouseleave', endDrag);
    svg.addEventListener('touchstart', startDrag);
    svg.addEventListener('touchmove', drag);
    svg.addEventListener('touchend', endDrag);
    svg.addEventListener('touchleave', endDrag);
    svg.addEventListener('touchcancel', endDrag);

    function startDrag(event) {
        event.preventDefault();
        dragging = true;
        var clientX, clientY;
        if (event.type === 'touchstart') {
            clientX = event.touches[0].clientX;
            clientY = event.touches[0].clientY;
        } else {
            clientX = event.clientX;
            clientY = event.clientY;
        }
        var pt = svg.createSVGPoint();
        pt.x = clientX;
        pt.y = clientY;
        var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
        offset.x = svgP.x - parseFloat(circle.getAttribute('cx'));
        offset.y = svgP.y - parseFloat(circle.getAttribute('cy'));
    }

    function drag(event) {
        if (dragging) {
            event.preventDefault();
            var clientX, clientY;
            if (event.type === 'touchmove') {
                clientX = event.touches[0].clientX;
                clientY = event.touches[0].clientY;
            } else {
                clientX = event.clientX;
                clientY = event.clientY;
            }
            var pt = svg.createSVGPoint();
            pt.x = clientX;
            pt.y = clientY;
            var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());

            var x = svgP.x - offset.x;
            var y = svgP.y - offset.y;

            // check if the circle is inside any of the rectangles
            var rect = getRectContainingPoint(x, y);
            if (rect) {
                // check if the circle is inside the rectangle
            var rectLeft = rect.x + circleRadius / 2;
            var rectRight = rect.x + rect.width - circleRadius / 2;
            var rectTop = rect.y + circleRadius / 2;
            var rectBottom = rect.y + rect.height - circleRadius / 2;
                            if (x < rectLeft) {
                x = rectLeft;
                offset.x = svgP.x - parseFloat(circle.getAttribute('cx'));
            } else if (x > rectRight) {
                x = rectRight;
                offset.x = svgP.x - parseFloat(circle.getAttribute('cx'));
            }
            if (y < rectTop) {
                y = rectTop;
                offset.y = svgP.y - parseFloat(circle.getAttribute('cy'));
            } else if (y > rectBottom) {
                y = rectBottom;
                offset.y = svgP.y - parseFloat(circle.getAttribute('cy'));
            }

            // update the circle position and radius
            circle.setAttribute('cx', x);
            circle.setAttribute('cy', y);
            if (circle.getAttribute('r') != circleRadius / 2) {
                circle.setAttribute('r', circleRadius / 2);
            }
        } else {
            // update the circle position and radius
            circle.setAttribute('cx', x);
            circle.setAttribute('cy', y);
            if (circle.getAttribute('r') != circleRadius * 2 && getRectContainingPoint(circle.getAttribute('cx'), circle.getAttribute('cy'))) {
                circle.setAttribute('r', circleRadius * 2);
            }
        }
    }
}

function endDrag(event) {
    dragging = false;
}

function getRectContainingPoint(x, y) {
    if (x >= rect1.x && x <= rect1.x + rect1.width && y >= rect1.y && y <= rect1.y + rect1.height) {
        return rect1;
    } else if (x >= rect2.x && x <= rect2.x + rect2.width && y >= rect2.y && y <= rect2.y + rect2.height) {
        return rect2;
    } else if (x >= rect3.x && x <= rect3.x + rect3.width && y >= rect3.y && y <= rect3.y + rect3.height) {
        return rect3;
    } else {
        return null;
    }
}
rect {
  fill         : lightblue;
  stroke       : black;
  stroke-width : 2;
}

circle {
  fill         : red;
  stroke       : black;
  stroke-width : 2;
  cursor       : move;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
    <rect x="50" y="50" width="100" height="100"/>
    <rect x="200" y="50" width="100" height="100"/>
    <rect x="350" y="50" width="100" height="100"/>
    <circle cx="250" cy="250" r="30" id="myCircle"/>
</svg>

javascript svg
1个回答
0
投票

一种方法。
但实际上,您必须使用Drag & Drop API

const 
  mySvg  = document.querySelector('#my-svg')
, Ball   = mySvg.querySelector('circle')
, rects  = [...mySvg.querySelectorAll('rect')].map( ElmX =>
    { 
    let { x, y, width, height } = ElmX.getBBox();
    return {x, y, xE: x + width, yE: y + height, rectElm: ElmX }
    })
, mouseSVG = (()=>
    {
    const 
      vb  = mySvg.viewBox.baseVal
    , bcr = mySvg.getBoundingClientRect()
    , wr  = bcr.width / vb.width    // width ratio
    , hr  = bcr.height / vb.height  // height ration 
    , x0  = bcr.left - (vb.x * wr)  // svg X 0 pixel page position
    , y0  = bcr.top  - (vb.y * hr)  // svg Y 0 pixel page position
      ;
    return ({pageX,pageY}) => ({mX: Math.round((pageX - x0) / wr),mY: Math.round((pageY - y0) / hr) });
    })()
, status = 
    { onGrab     : false  // ball follow cursor...
    , freeBall   : true   // no box caught her !
    , rectTarget : null
    , mzX        : 0      // mouse pageX Position on grab start
    , mzY        : 0      // mouse pageX Position on grab start
    , btX        : 0      // ball translation X
    , btY        : 0      // ball translation Y
    , bcX        : Ball.cx.baseVal.value
    , bcY        : Ball.cy.baseVal.value
    }
, inBox = {}
  ;
Ball.onmousedown  = e =>
  {
  // if (status.freeBall)
  //   {
    Ball.classList.add('grabMove');
    mySvg.classList.add('grabMove');
    let { mX, mY } = mouseSVG(e);
    status.onGrab  = true;
    status.mzX     = mX - status.btX;
    status.mzY     = mY - status.btY;
    // }
  }
mySvg.onmousemove = mousemoveFree;
mySvg.onmouseup   = endGrabFree;
mySvg.onmouseout  = endGrabFree;

function mousemoveFree(e)
  {
  if ( status.onGrab )
    {
    let {mX,mY} = mouseSVG(e);
    status.btX  =  mX - status.mzX;
    status.btY  =  mY - status.mzY;

    Ball.setAttribute('transform', `translate(${status.btX},${status.btY})`);

    let x = rects.find(r=>r.x<mX && mX<r.xE && r.y<mY && mY<r.yE)?.rectElm;
    if ( !!x )
      {
      if (x !== status.rectTarget) x.setAttribute('stroke','yellow'); 
      }
    else if (!!status.rectTarget ) status.rectTarget.setAttribute('stroke','black');
    status.rectTarget = x;
    }
  }  
function mouseMoveBox(e)
  {
  if ( status.onGrab )
    {
    let {mX,mY} = mouseSVG(e);

    if (inBox.x < mX && mX < inBox.xE && inBox.y < mY && mY < inBox.yE )
      {
      status.btX  =  mX - status.mzX;
      status.btY  =  mY - status.mzY;
      let      
        ball_x = status.bcX + status.btX
      , ball_y = status.bcY + status.btY
        ;
      Ball.setAttribute('transform', `translate(${status.btX},${status.btY})`);
      }
    else
      {
      endGrabBox();
      }
    }  
  }
function endGrabFree(e)
  {
  Ball.classList.remove('grabMove');
  mySvg.classList.remove('grabMove');
  status.onGrab = false;

  if (!!status.rectTarget && status.freeBall )
    {
    mySvg.onmousemove = mouseMoveBox;
    mySvg.onmouseup   = endGrabBox;
    mySvg.onmouseout  = endGrabBox;

    status.freeBall = false;
    status.rectTarget.setAttribute('stroke','black');

    let      
      ball_x                  = status.bcX + status.btX
    , ball_y                  = status.bcY + status.btY
    , ball_r                  = Ball.r.baseVal.value  / 2
    , { x, y, width, height } = status.rectTarget.getBBox()
    , xE =  x + width 
    , yE =  y + height
      ;
    Object.assign( inBox, { x, y, xE, yE, ball_r } ) 
 
    Ball.r.baseVal.value = ball_r;

    Ball.setAttribute('transform', '');
    ball_x = Math.max(  x + ball_r, ball_x );
    ball_y = Math.max(  y + ball_r, ball_y );
    ball_x = Math.min( xE - ball_r, ball_x );
    ball_y = Math.min( yE - ball_r, ball_y );
    status.btX = 0;
    status.btY = 0;

    Ball.cx.baseVal.value = status.bcX = ball_x;
    Ball.cy.baseVal.value = status.bcY = ball_y;
    }
  }
function endGrabBox(e)
  {
  if (!status.onGrab)
    return;

  Ball.classList.remove('grabMove');
  mySvg.classList.remove('grabMove');
  status.onGrab = false;
 
  let      
    ball_x = status.bcX + status.btX
  , ball_y = status.bcY + status.btY
    ;
  ball_x = Math.max(  inBox.x + inBox.ball_r, ball_x );
  ball_y = Math.max(  inBox.y + inBox.ball_r, ball_y );
  ball_x = Math.min( inBox.xE - inBox.ball_r, ball_x );
  ball_y = Math.min( inBox.yE - inBox.ball_r, ball_y );

  status.btX = 0;
  status.btY = 0;

  Ball.setAttribute('transform', '');

  Ball.cx.baseVal.value = status.bcX = ball_x;
  Ball.cy.baseVal.value = status.bcY = ball_y;
  }
svg {
  margin     : 1rem;
  width      : 900px;
  height     : 750px;
  background : #021d6e;
  }
rect {
  fill         : lightblue;
  stroke-width : 2;
  }
circle {
  fill         : red;
  stroke       : black;
  stroke-width : 2;
  cursor       : grab;
  }
circle.grabMove,
svg.grabMove {
  cursor       : grabbing !important;
  }
<svg id="my-svg" viewBox="-50 -100 600 500"> 
  <rect x="50"  y="50" width="100" height="100" stroke="black"/>
  <rect x="200" y="50" width="100" height="100" stroke="black"/>
  <rect x="350" y="50" width="100" height="100" stroke="black"/>
  <circle cx="250" cy="250" r="30" transform="" />
</svg>

© www.soinside.com 2019 - 2024. All rights reserved.