在可拖动元素上模拟 3 像素拖动

Posted

技术标签:

【中文标题】在可拖动元素上模拟 3 像素拖动【英文标题】:simulate a 3 pixel drag on draggable elem 【发布时间】:2020-08-06 03:10:26 【问题描述】:

我只是想模拟一个小的点击并拖动一个可拖动的 div 元素——我在这里找到了几个类似的问题,但都涉及使用额外的插件......

是否有纯 javascript 或 jQuery 功能来专门处理拖动?据我所知.click(); 或鼠标按下可以被调用来启动。

我并不是要创建拖放功能,我已经拥有了。我正在尝试创建一个自动模拟此事件的小函数。 单击 > 按住 > 向上拖动 3 个像素


更新:在不使用第三方库的情况下,在 SO 或其他地方找不到任何关于此的内容,因此创建了 500 赏金。当然有可能。

【问题讨论】:

我对此一无所知。您必须为该自定义“模拟”创建动画。由于您的问题中没有代码,因此我将投票结束,因为需要更多关注...旧术语太宽泛会很准确。 @LouysPatriceBessette 不错,不错 您是否尝试过触发按键和鼠标移动事件? 如果您想自动激活现有的拖放行为,您应该向我们展示您是如何实现该功能的。 【参考方案1】:

您可以使用MouseEvent 接口模拟鼠标事件和使用DragEvent 接口拖动事件。您必须使用EventTarget.dispatchEvent() 触发正确的序列。

我猜你将"html Drag and Drop API" 与"Mouse events" 结合起来创建拖放行为,所以我将提供一个模拟拖放和一个模拟鼠标捕获和鼠标移动

模拟鼠标捕获和鼠标移动

请阅读内联 cmets

// We create 3 mouse events using MouseEvent interface,
// one for mousedown to initiate the process,
// one for mousemove to handle to movement
// and one for mouseup to terminate the process

const mouseDownEvent = new MouseEvent('mousedown', 
  clientX: element.getBoundingClientRect().left,
  clientY: element.getBoundingClientRect().top,
  bubbles: true,
  cancelable: true
);

const mouseMoveEvent = new MouseEvent('mousemove', 
  clientX: element.getBoundingClientRect().left + 3,
  clientY: element.getBoundingClientRect().top,
  bubbles: true,
  cancelable: true
);

const mouseUpEvent = new MouseEvent('mouseup', 
  bubbles: true,
  cancelable: true
);

// Dispatch the mousedown event to the element that has the listener
element.dispatchEvent(mouseDownEvent);

// For mousemove, the listener may be the parent or even the document
<element|document>.dispatchEvent(mouseMoveEvent);

// Dispatch mouseup to terminate the process
element.dispatchEvent(mouseUpEvent);

运行代码sn-p。方形 div 元素是可拖动的,您可以在模拟计时器未运行时单击并拖动它。单击模拟按钮以查看实际的模拟:

// Timer ID holder
let linearSimulationTimer;

// Simulation X/Y calculation
let calcX = 0, calcY = 0;

// Simulation X/Y axis orientation to handle parent collisions
let xAxisOrientation = 1, yAxisOrientation = 1;

// How many pixels to move the element for X/Y axis
const pixelsShift = 3;

// Elements
const simulateButton = document.getElementById('simulate-dnm');
const movable = document.getElementById('movable');
const movableContainer = movable.parentNode;

simulateButton.addEventListener('click', function()   
  // If there is a timer running
  if (linearSimulationTimer) 
    // Stop and clear the timer
    clearInterval(linearSimulationTimer);
    linearSimulationTimer = null;
    
    // Create a simple mouseup event with no custom properties
    const mouseUpEvent = new MouseEvent('mouseup', 
      bubbles: true,
      cancelable: true,
    );
    
    // Dispatch it to the movable element
    movable.dispatchEvent(mouseUpEvent);

    // Handle button label text (start/stop)
    simulateButton.classList.remove('running');
  // Else if there is no timer running
   else 
    // Create the mousedown event using movable element client left/top for event clientX.clientY
    const mouseDownEvent = new MouseEvent('mousedown', 
      clientX: movable.getBoundingClientRect().left,
      clientY: movable.getBoundingClientRect().top,
      pageX: 0,
      pageY: 0,
      bubbles: true,
      cancelable: true,
      view: window
    );

    // Dispatch the mousedown event to the movable element
    movable.dispatchEvent(mouseDownEvent);

    // Get movable parent client rect
    const parentRect = movable.parentNode.getBoundingClientRect();

    // Start the simulation timer
    linearSimulationTimer = setInterval(() => 
      // Get movable element size
      const  width, height  = movable.getBoundingClientRect();

      // Calculate new X/Y position and orientation
      calcX += pixelsShift * xAxisOrientation;
      calcY += pixelsShift * yAxisOrientation;

      // If we hit movable parent X axis bounds, reverse X axis
      if (calcX + width > parentRect.width) 
        calcX  = parentRect.width - width;
        xAxisOrientation = -xAxisOrientation;
       else if (calcX < 0) 
        calcX = 0;
        xAxisOrientation = -xAxisOrientation;
      

      // If we hit movable parent Y axis bounds, reverse Y axis
      if (calcY + height > parentRect.height) 
        calcY  = parentRect.height - height;
        yAxisOrientation = -yAxisOrientation;
       else if (calcY < 0) 
        calcY = 0;
        yAxisOrientation = -yAxisOrientation;
      

      // Create mousemove event using calcX/calcY and the parent client position
      const mouseMoveEvent = new MouseEvent('mousemove', 
        clientX: parentRect.left + calcX,
        clientY: parentRect.top + calcY,
        pageX: 0,
        pageY: 0,
        bubbles: true,
        cancelable: true,
        view: window
      );

      // Dispatch the mousemove event to the parent which it has the listener
      movableContainer.dispatchEvent(mouseMoveEvent);
    , 50);
    
    // Handle button label text (start/stop)
    simulateButton.classList.add('running');
  
);

// Mouse capture and drag handler (https://javascript.info/mouse-drag-and-drop)
movable.onmousedown = function(event) 
  let shiftX = event.clientX - movable.getBoundingClientRect().left;
  let shiftY = event.clientY - movable.getBoundingClientRect().top;

  moveAt(event.pageX, event.pageY);

  function moveAt(pageX, pageY) 
    movable.style.left = pageX - shiftX - movableContainer.offsetLeft + 'px';
    movable.style.top = pageY - shiftY - movableContainer.offsetTop + 'px';
  

  function onMouseMove(event) 
    moveAt(event.pageX, event.pageY);
  

  movableContainer.addEventListener('mousemove', onMouseMove);

  movable.onmouseup = function() 
    movableContainer.removeEventListener('mousemove', onMouseMove);
    movable.onmouseup = null;
  


movable.ondragstart = function() 
  return false;
#movable-container 
  position: relative;
  height: 80px;
  width: 200px;
  margin: auto;
  margin-bottom: 20px;
  border: 1px dotted silver;


#movable 
  position: relative;
  left: 0;
  width: 30px;
  height: 30px;
  background-color: cornflowerblue;
  border-radius: 5px;
  border: 1px solid grey;


#simulate-dnm > span:before 
  content: 'Start ';


#simulate-dnm.running > span:before 
  content: 'Stop ';
<div id="movable-container">
  <div id="movable"></div>
</div>
<div>
  <button id="simulate-dnm"><span>Mouse capture & move simulation</span></button>
</div>

模拟拖放

请阅读内联 cmets

// We create 3 drag events using DragEvent interface,
// one for dragstart to initiate the process,
// one for drop to handle the drag element drop inside the drop container
// and one for dragend to terminate the process

const dragStartEvent = new DragEvent('dragstart', 
  bubbles: true,
  cancelable: true
);
const dragEndEvent = new DragEvent('dragend', 
  bubbles: true,
  cancelable: true
);
const dropEvent = new DragEvent('drop', 
  bubbles: true,
  cancelable: true
);

// Dispatch the dragstart event to the source element to initiate
sourceNode.dispatchEvent(dragStartEvent);

// Dispatch the drop event to the destination element to get the drag
destinationNode.dispatchEvent(dropEvent);

// Dispatch the dragend event to the source element to finish
sourceNode.dispatchEvent(dragEndEvent);

运行代码 sn-p 并点击模拟按钮以触发拖放事件序列:

// The current parent index that the drag element is inside
let currentParentIndex = 0;

// Elements
const simulateButton = document.getElementById('simulate-dnd');
const sourceNode = document.getElementById('drag');

function simulateDragDrop(sourceNode, destinationNode) 
  // Create dragstart event
  const dragStartEvent = new DragEvent('dragstart', 
    bubbles: true,
    cancelable: true
  );
  
  // Create dragend event
  const dragEndEvent = new DragEvent('dragend', 
    bubbles: true,
    cancelable: true
  );
  
  // Create drop event
  const dropEvent = new DragEvent('drop', 
    bubbles: true,
    cancelable: true
  );

  // Dispatch dragstart event to the draggable element
  sourceNode.dispatchEvent(dragStartEvent);
  
  // Dispatch drop event to container element we want to drop the draggable
  destinationNode.dispatchEvent(dropEvent);
  
  // Dispatch dragend  event to the draggable element
  sourceNode.dispatchEvent(dragEndEvent);


simulateButton.addEventListener('click', function() 
  // Change drop container index to other container than the current
  const newParentIndex = currentParentIndex === 0 ? 1 : 0;
  
  // Get the drop container element
  const destinationNode = document.getElementsByClassName('dropzone')[newParentIndex];

  // Initiate simulation sequence
  simulateDragDrop(sourceNode, destinationNode);

  // Save the new container index
  currentParentIndex = newParentIndex;
);


// Drag n Drop handling

let dragged;

document.addEventListener("dragstart", function(event) 
  // store a ref. on the dragged elem
  dragged = event.target;
  // make it half transparent
  event.target.style.opacity = .5;
, false);

document.addEventListener("dragend", function(event) 
  // reset the transparency
  event.target.style.opacity = "";
, false);

/* events fired on the drop targets */
document.addEventListener("dragover", function(event) 
  // prevent default to allow drop
  event.preventDefault();
, false);

document.addEventListener("dragenter", function(event) 
  // highlight potential drop target when the draggable element enters it
  if (event.target.className == "dropzone") 
    event.target.style.background = "yellow";
  
, false);

document.addEventListener("dragleave", function(event) 
  // reset background of potential drop target when the draggable element leaves it
  if (event.target.className == "dropzone") 
    event.target.style.background = "";
  
, false);

document.addEventListener("drop", function(event) 
  // prevent default action (open as link for some elements)
  event.preventDefault();
  // move dragged elem to the selected drop target
  if (event.target.className == "dropzone") 
    event.target.style.background = "";
    dragged.parentNode.removeChild(dragged);
    event.target.appendChild(dragged);
    currentParentIndex = Array.prototype.indexOf.call(event.target.parentNode.children, event.target);
    
, false);
.dropzones 
  display: flex;
  justify-content: space-evenly;


.dropzone 
  width: 100px;
  height: 100px;
  background-color: mintcream;
  border-radius: 5px;
  border: 2px dashed darkgrey;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 10px;


#drag 
  margin: unset;
  width: 40px;
  height: 40px;
  background-color: coral;
  border-radius: 4px;
  border: 1px solid grey; 
<div class="dropzones">
  <div class="dropzone">
    <div id="drag" draggable="true"></div>
  </div>
  <div class="dropzone"></div>
</div>
<div>
  <button id="simulate-dnd">Simulate Drag & Drop</button>
</div>

【讨论】:

【参考方案2】:

如果您的意思是“模拟拖动”,则触发 mousedown 事件,然后更新元素的位置,您应该能够执行以下操作:

let element = document.getElementById('draggable-div');
element.dispatchEvent(new Event('mousedown'));
element.style.top += 3

style.top 更改可能会也可能不会移动元素,具体取决于元素的定位方式(绝对、相对、粘性)。

如果您有尝试触发的拖放库。然后,您甚至可以通过简单地将元素移动到放置区域(向上 3 个像素)然后触发 mouseup 来模拟拖动:

let element = document.getElementById('draggable-div');
element.style.top += 3
element.dispatchEvent(new Event('mouseup'));

这个列表还在继续,因为现在还有拖动事件(尽管它们还没有被所有浏览器完全支持:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API)。

但你的问题并不清楚你要进行什么样的模拟。

【讨论】:

这很奇怪,我看到 DOM 元素上对 dispatchEvent 的支持可以追溯到很久以前。我们不是在拖动 DOM 元素吗? developer.mozilla.org/en-US/docs/Web/API/EventTarget/… @docholiday 你能告诉我们你在哪里试过dispatchEvent的代码吗? 还想补充一下有拖动事件

以上是关于在可拖动元素上模拟 3 像素拖动的主要内容,如果未能解决你的问题,请参考以下文章

如何使嵌套元素在可拖动容器中不可拖动?

网页模拟QQ面板的拖动效果

jQuery 可拖动和可拖放,在可拖动 ul 上滚动

如何在可排序列表中排除元素被拖动?

Java Selenium Actions模拟鼠标拖动dragAndDrop总结

向父母发出传播模拟鼠标事件(拖动目的)的问题