我正在编写一些 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>
一种方法。
但实际上,您必须使用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>