如何在父DIV容器中移动和调整大小DIV,而不会溢出?

huangapple go评论61阅读模式
英文:

How to move and resize DIVs in a parent DIV container without overflowing?

问题

我理解你想要的功能是限制小部件在紫色div内,并确保鼠标始终从它开始拖动,即使碰到边界也必须回到初始位置。以下是可能的解决方案:

首先,你可以使用以下方式修改moveElement函数,以确保小部件不会越过紫色div的边界:

function moveElement(event) {
  const x = event.clientX - shiftX;
  const y = event.clientY - shiftY;

  // 计算小部件的新位置
  const newX = Math.max(0, Math.min(purpleDiv.clientWidth - widget.clientWidth, x));
  const newY = Math.max(0, Math.min(purpleDiv.clientHeight - widget.clientHeight, y));

  widget.style.left = newX + 'px';
  widget.style.top = newY + 'px';
}

在此代码中,我们使用Math.maxMath.min来确保小部件不会超出紫色div的边界。purpleDiv是紫色div的DOM元素,你需要在代码中找到它并将其分配给purpleDiv变量。

接下来,为了使鼠标在碰到边界时返回到初始位置,你可以在mousedown事件处理程序中记录鼠标初始位置,然后在moveElement函数中将鼠标移到初始位置,如下所示:

let initialX, initialY;

document.addEventListener('mousedown', (event) => {
  // ... (省略其他代码)

  draggedElements.forEach((widget) => {
    const shiftX = event.clientX - widget.getBoundingClientRect().left;
    const shiftY = event.clientY - widget.getBoundingClientRect().top;

    initialX = event.clientX;
    initialY = event.clientY;

    function moveElement(event) {
      const x = initialX - shiftX;
      const y = initialY - shiftY;

      // ... (省略其他代码)

      widget.style.left = newX + 'px';
      widget.style.top = newY + 'px';

      // 移动鼠标到初始位置
      window.requestAnimationFrame(() => {
        window.scrollTo(initialX, initialY);
      });
    }

    // ... (省略其他代码)
  });
});

这样,在拖动小部件时,鼠标会在碰到边界时返回到初始位置。

请确保在代码中找到紫色div的DOM元素,并将其分配给purpleDiv变量。这些代码修改应该有助于实现你所描述的行为。

英文:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

  let isSelecting = false;
let selectionStartX, selectionStartY, selectionEndX, selectionEndY;
let selectionRectangle;
let draggedElements = [];
const widgets = document.querySelectorAll(&#39;.widgets&#39;);
const widgetContainer = document.getElementById(&#39;widget-container&#39;);

document.addEventListener(&#39;mousedown&#39;, (event) =&gt; {
  const header = event.target.closest(&#39;.widget-header&#39;);
  if (header) {
    const widget = header.parentElement;
    // If the clicked widget is not selected
    if (!widget.classList.contains(&#39;selected&#39;)) {
      // deselect all widgets 
      widgets.forEach((widget) =&gt; {
        widget.classList.remove(&#39;selected&#39;);
      });
      // and only drag the clicked one
      draggedElements = [widget];
    } else {
      // otherwise drag the selected 
      draggedElements = Array.from(widgets).filter((widget) =&gt; widget.classList.contains(&#39;selected&#39;));
    }

    draggedElements.forEach((widget) =&gt; {
      const shiftX = event.clientX - widget.getBoundingClientRect().left;
      const shiftY = event.clientY - widget.getBoundingClientRect().top;

      function moveElement(event) {
        const x = event.clientX - shiftX;
        const y = event.clientY - shiftY;

        widget.style.left = x + &#39;px&#39;;
        widget.style.top = y + &#39;px&#39;;
      }

      function stopMoving() {
        document.removeEventListener(&#39;mousemove&#39;, moveElement);
        document.removeEventListener(&#39;mouseup&#39;, stopMoving);
      }

      document.addEventListener(&#39;mousemove&#39;, moveElement);
      document.addEventListener(&#39;mouseup&#39;, stopMoving);
    });

    return;
  }

  const isWidgetClicked = Array.from(widgets).some((widget) =&gt; {
    const widgetRect = widget.getBoundingClientRect();
    return (
      event.clientX &gt;= widgetRect.left &amp;&amp;
      event.clientX &lt;= widgetRect.right &amp;&amp;
      event.clientY &gt;= widgetRect.top &amp;&amp;
      event.clientY &lt;= widgetRect.bottom
    );
  });

  if (!isWidgetClicked &amp;&amp; event.target !== selectionRectangle) { // Check if the target is not the selection rectangle
    isSelecting = true;
    selectionStartX = event.clientX;
    selectionStartY = event.clientY;
    selectionRectangle = document.createElement(&#39;div&#39;);
    selectionRectangle.id = &#39;selection-rectangle&#39;;
    selectionRectangle.style.position = &#39;absolute&#39;;
    selectionRectangle.style.border = &#39;2px dashed blue&#39;;
    selectionRectangle.style.pointerEvents = &#39;none&#39;;
    selectionRectangle.style.display = &#39;none&#39;;
    document.body.appendChild(selectionRectangle);

    // Remove selected class from widgets
    widgets.forEach((widget) =&gt; {
      widget.classList.remove(&#39;selected&#39;);
    });
  }
});

document.addEventListener(&#39;mousemove&#39;, (event) =&gt; {
  if (isSelecting) {
    selectionEndX = event.clientX;
    selectionEndY = event.clientY;

    let width = Math.abs(selectionEndX - selectionStartX);
    let height = Math.abs(selectionEndY - selectionStartY);

    selectionRectangle.style.width = width + &#39;px&#39;;
    selectionRectangle.style.height = height + &#39;px&#39;;
    selectionRectangle.style.left = Math.min(selectionEndX, selectionStartX) + &#39;px&#39;;
    selectionRectangle.style.top = Math.min(selectionEndY, selectionStartY) + &#39;px&#39;;
    selectionRectangle.style.display = &#39;block&#39;;

    widgets.forEach((widget) =&gt; {
      const widgetRect = widget.getBoundingClientRect();
      const isIntersecting = isRectangleIntersecting(widgetRect, {
        x: Math.min(selectionStartX, selectionEndX),
        y: Math.min(selectionStartY, selectionEndY),
        width,
        height,
      });
      if (isIntersecting) {
        widget.classList.add(&#39;selected&#39;);
      } else {
        widget.classList.remove(&#39;selected&#39;);
      }
    });
  }
});

document.addEventListener(&#39;mouseup&#39;, () =&gt; {
  if (isSelecting) {
    isSelecting = false;
    selectionRectangle.remove();
  }
});

function isRectangleIntersecting(rect1, rect2) {
  return (
    rect1.left &gt;= rect2.x &amp;&amp;
    rect1.top &gt;= rect2.y &amp;&amp;
    rect1.right &lt;= rect2.x + rect2.width &amp;&amp;
    rect1.bottom &lt;= rect2.y + rect2.height
  );
}

<!-- language: lang-css -->

  #selection-rectangle {
  position: absolute;
  border: 2px dashed blue;
  pointer-events: none;
  display: none;
  z-index: 9999999;
}

.widgets.selected {
  outline-color: blue;
  outline-width: 2px;
  outline-style: dashed;
}

.widgets {
  position: absolute;
  z-index: 9;
  background-color: #000000;
  color: white;
  font-size: 25px;
  font-family: Arial, Helvetica, sans-serif;
  border: 2px solid #212128;
  text-align: center;
  width: 500px;
  height: 200px;
  min-width: 166px;
  min-height: 66px;
  overflow: hidden;
  resize: both;
  image-resolution: none;
  border-radius: 10px;
}

.widget-header {
  padding: 10px;
  cursor: move;
  z-index: 10;
  background-color: #040c14;
  outline-color: white;
  outline-width: 2px;
  outline-style: solid;
}

#purple-div {
  width: 1000px;
  height: 700px;
  background-color: purple;
  position: absolute;
}

<!-- language: lang-html -->

  &lt;div id=&quot;widget1&quot; class=&quot;widgets&quot; style=&quot;left: 50px; top: 50px;&quot;&gt;
    &lt;div id=&quot;widget1header&quot; class=&quot;widget-header&quot;&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;div id=&quot;widget2&quot; class=&quot;widgets&quot; style=&quot;left: 150px; top: 150px;&quot;&gt;
    &lt;div id=&quot;widget2header&quot; class=&quot;widget-header&quot;&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;div id=&quot;widget3&quot; class=&quot;widgets&quot; style=&quot;left: 250px; top: 250px;&quot;&gt;
    &lt;div id=&quot;widget3header&quot; class=&quot;widget-header&quot;&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;div id=&quot;purple-div&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

I want the widgets to be restricted by the purple div and I don't know how to do it. The mouse also has to always be dragged from where it started to drag, that means if it hits the border, the mouse has to go back to where it was dragging the widget from in order to move(while having the mouse down, that means dragging).

答案1

得分: 2

以下是您提供的代码的翻译部分:

const container = document.querySelector('.container');

const draggables = document.querySelectorAll('.draggable');

draggables.forEach(elem => {
  makeDraggableResizable(elem);
  elem.addEventListener('mousedown', () => {
     const maxZ = Math.max(...[...draggables].map(elem => parseInt(getComputedStyle(elem)['z-index']) || 0));
     elem.style['z-index'] = maxZ + 1;
  });
});

function makeDraggableResizable(draggable){

  const move = (x, y) => {

    x = state.fromX + (x - state.startX);
    y = state.fromY + (y - state.startY);

    // 不允许移出容器外
    if (x < 0) x = 0;
    else if (x + draggable.offsetWidth > container.offsetWidth) x = container.offsetWidth - draggable.offsetWidth;

    if (y < 0) y = 0;
    else if (y + draggable.offsetHeight > container.offsetHeight) y = container.offsetHeight - draggable.offsetHeight;

    draggable.style.left = x + 'px';
    draggable.style.top = y + 'px';
  };

  const resize = (x, y) => {

    x = state.fromWidth + (x - state.startX);
    y = state.fromHeight + (y - state.startY);

    // 不允许移出容器外
    if (state.fromX + x > container.offsetWidth) x = container.offsetWidth - state.fromX;

    if (state.fromY + y > container.offsetHeight ) y = container.offsetHeight - state.fromY;

    draggable.style.width = x + 'px';
    draggable.style.height = y + 'px';
  };

  const listen = (op = 'add') => 
    Object.entries(listeners).slice(1)
      .forEach(([name, listener]) => document[op + 'EventListener'](name, listener));

  const state = new Proxy({}, {
    set(state, prop, val){
      const out = Reflect.set(...arguments);
      const ops = {
        startY: () => {
          listen();
          const style = getComputedStyle(draggable);
          [state.fromX, state.fromY] = [parseInt(style.left), parseInt(style.top)];
          [state.fromWidth, state.fromHeight] = [parseInt(style.width), parseInt(style.height)];
        },
        dragY: () => state.action(state.dragX, state.dragY),
        stopY: () => listen('remove') + state.action(state.stopX, state.stopY),
      };
      // 使用已解决的 Promise 来延迟移动作为微任务,以确保状态突变的顺序不重要
      ops[prop] && Promise.resolve().then(ops[prop]);
      return out;
    }
  });

  const listeners = {
    mousedown: e => Object.assign(state, {startX: e.pageX, startY: e.pageY}),
    // 这里首先提供了 dragY 以检查属性顺序是否重要
    mousemove: e => Object.assign(state, {dragY: e.pageY, dragX: e.pageX}),
    mouseup: e => Object.assign(state, {stopX: e.pageX, stopY: e.pageY}),
  };

  for(const [name, action] of Object.entries({move, resize})){
    draggable.querySelector(`.${name}`).addEventListener('mousedown', e => {
      state.action = action;
      listeners.mousedown(e);
    }); 
  }

}
html,body{
  height:100%;
  margin:0;
  padding:0;
}
*{
  box-sizing: border-box;
}

.draggable{
  position: absolute;
  padding:45px 15px 15px 15px;
  border-radius:4px;
  background:#ddd;
  user-select: none;
  left: 15px;
  top: 15px;
  min-width:200px;
}
.draggable>.move{
  line-height: 30px;
  padding: 0 15px;
  background:#bbb;
  border-bottom: 1px solid #777;
  cursor:move;
  position:absolute;
  left:0;
  top:0;
  height:30px;
  width:100%;
  border-radius: 4px 4px 0;
}
.draggable>.resize{
  cursor:nw-resize;
  position:absolute;
  right:0;
  bottom:0;
  height:16px;
  width:16px;
  border-radius: 0 0 4px 0;
  background: linear-gradient(to left top, #777 50%, transparent 50%)
}
.container{
  left:15px;
  top:15px;
  background: #111;
  border-radius:4px;
  width:calc(100% - 30px);
  height:calc(100% - 30px);
  position: relative;
}
<div class="container">
  <div class="draggable"><div class="move">可拖动的句柄</div>不可拖动的内容<div class="resize"></div></div>
  <div class="draggable" style="left:230px;"><div class="move">可拖动的句柄</div>不可拖动的内容<div class="resize"></div></div>
</div>
英文:

Continued from my answer: https://stackoverflow.com/questions/76420174/is-there-a-design-pattern-for-logic-performed-on-multiple-event-listeners/76420340#76420340

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

const container = document.querySelector(&#39;.container&#39;);
const draggables = document.querySelectorAll(&#39;.draggable&#39;);
draggables.forEach(elem =&gt; {
makeDraggableResizable(elem);
elem.addEventListener(&#39;mousedown&#39;, () =&gt; {
const maxZ = Math.max(...[...draggables].map(elem =&gt; parseInt(getComputedStyle(elem)[&#39;z-index&#39;]) || 0));
elem.style[&#39;z-index&#39;] = maxZ + 1;
});
});
function makeDraggableResizable(draggable){
const move = (x, y) =&gt; {
x = state.fromX + (x - state.startX);
y = state.fromY + (y - state.startY);
// don&#39;t allow moving outside the container
if (x &lt; 0) x = 0;
else if (x + draggable.offsetWidth &gt; container.offsetWidth) x = container.offsetWidth - draggable.offsetWidth;
if (y &lt; 0) y = 0;
else if (y + draggable.offsetHeight &gt; container.offsetHeight) y = container.offsetHeight - draggable.offsetHeight;
draggable.style.left = x + &#39;px&#39;;
draggable.style.top = y + &#39;px&#39;;
};
const resize = (x, y) =&gt; {
x = state.fromWidth + (x - state.startX);
y = state.fromHeight + (y - state.startY);
// don&#39;t allow moving outside the container
if (state.fromX + x &gt; container.offsetWidth) x = container.offsetWidth - state.fromX;
if (state.fromY + y &gt; container.offsetHeight ) y = container.offsetHeight - state.fromY;
draggable.style.width = x + &#39;px&#39;;
draggable.style.height = y + &#39;px&#39;;
};
const listen = (op = &#39;add&#39;) =&gt; 
Object.entries(listeners).slice(1)
.forEach(([name, listener]) =&gt; document[op + &#39;EventListener&#39;](name, listener));
const state = new Proxy({}, {
set(state, prop, val){
const out = Reflect.set(...arguments);
const ops = {
startY: () =&gt; {
listen();
const style = getComputedStyle(draggable);
[state.fromX, state.fromY] = [parseInt(style.left), parseInt(style.top)];
[state.fromWidth, state.fromHeight] = [parseInt(style.width), parseInt(style.height)];
},
dragY: () =&gt; state.action(state.dragX, state.dragY),
stopY: () =&gt; listen(&#39;remove&#39;) + state.action(state.stopX, state.stopY),
};
// use a resolved Promise to postpone the move as a microtask so
// the order of state mutation isn&#39;t important
ops[prop] &amp;&amp; Promise.resolve().then(ops[prop]);
return out;
}
});
const listeners = {
mousedown: e =&gt; Object.assign(state, {startX: e.pageX, startY: e.pageY}),
// here we first provide dragY to check that the order of props is not important
mousemove: e =&gt; Object.assign(state, {dragY: e.pageY, dragX: e.pageX}),
mouseup: e =&gt; Object.assign(state, {stopX: e.pageX, stopY: e.pageY}),
};
for(const [name, action] of Object.entries({move, resize})){
draggable.querySelector(`.${name}`).addEventListener(&#39;mousedown&#39;, e =&gt; {
state.action = action;
listeners.mousedown(e);
}); 
}
}

<!-- language: lang-css -->

html,body{
height:100%;
margin:0;
padding:0;
}
*{
box-sizing: border-box;
}
.draggable{
position: absolute;
padding:45px 15px 15px 15px;
border-radius:4px;
background:#ddd;
user-select: none;
left: 15px;
top: 15px;
min-width:200px;
}
.draggable&gt;.move{
line-height: 30px;
padding: 0 15px;
background:#bbb;
border-bottom: 1px solid #777;
cursor:move;
position:absolute;
left:0;
top:0;
height:30px;
width:100%;
border-radius: 4px 4px 0;
}
.draggable&gt;.resize{
cursor:nw-resize;
position:absolute;
right:0;
bottom:0;
height:16px;
width:16px;
border-radius: 0 0 4px 0;
background: linear-gradient(to left top, #777 50%, transparent 50%)
}
.container{
left:15px;
top:15px;
background: #111;
border-radius:4px;
width:calc(100% - 30px);
height:calc(100% - 30px);
position: relative;
}

<!-- language: lang-html -->

&lt;div class=&quot;container&quot;&gt;
&lt;div class=&quot;draggable&quot;&gt;&lt;div class=&quot;move&quot;&gt;draggable handle&lt;/div&gt;Non-draggable content&lt;div class=&quot;resize&quot;&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;draggable&quot; style=&quot;left:230px;&quot;&gt;&lt;div class=&quot;move&quot;&gt;draggable handle&lt;/div&gt;Non-draggable content&lt;div class=&quot;resize&quot;&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年6月22日 20:12:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76531761.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定