interview
frontend-classic
怎么使用 JS 实现元素拖拽功能

前端经典面试题合集, 怎么使用 JS 实现元素拖拽功能?

前端经典面试题合集, 怎么使用 JS 实现元素拖拽功能?

QA

Step 1

Q:: 如何使用 JavaScript 实现元素拖拽功能?

A:: 可以使用以下步骤实现元素拖拽功能:1. 为元素添加 'mousedown' 事件监听器,获取鼠标起始位置和元素起始位置。2. 在文档上添加 'mousemove' 事件监听器,根据鼠标移动距离更新元素位置。3. 在文档上添加 'mouseup' 事件监听器,移除 'mousemove' 和 'mouseup' 事件监听器。示例代码如下:

 
const draggable = document.getElementById('draggable');
let offsetX, offsetY;
draggable.addEventListener('mousedown', (e) => {
  offsetX = e.clientX - draggable.getBoundingClientRect().left;
  offsetY = e.clientY - draggable.getBoundingClientRect().top;
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
  draggable.style.left = `${e.clientX - offsetX}px`;
  draggable.style.top = `${e.clientY - offsetY}px`;
}
function onMouseUp() {
  document.removeEventListener('mousemove', onMouseMove);
  document.removeEventListener('mouseup', onMouseUp);
}
 

Step 2

Q:: 如何确保拖拽的元素不超出父容器的边界?

A:: 在 'mousemove' 事件处理函数中,添加边界检测逻辑。如果元素的新位置超出了父容器的边界,就将其位置限制在边界内。示例代码如下:

 
function onMouseMove(e) {
  const parentRect = draggable.parentElement.getBoundingClientRect();
  const newX = e.clientX - offsetX;
  const newY = e.clientY - offsetY;
  if (newX < parentRect.left) newX = parentRect.left;
  if (newX + draggable.offsetWidth > parentRect.right) newX = parentRect.right - draggable.offsetWidth;
  if (newY < parentRect.top) newY = parentRect.top;
  if (newY + draggable.offsetHeight > parentRect.bottom) newY = parentRect.bottom - draggable.offsetHeight;
  draggable.style.left = `${newX}px`;
  draggable.style.top = `${newY}px`;
}
 

Step 3

Q:: 如何实现拖拽过程中的流畅效果?

A:: 为了确保拖拽过程中的流畅效果,可以使用 CSS3 的 'transform' 属性代替 'style.left' 和 'style.top',因为 'transform' 属性在现代浏览器中通常更高效。示例代码如下:

 
let startX, startY;
draggable.addEventListener('mousedown', (e) => {
  startX = e.clientX;
  startY = e.clientY;
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
  const dx = e.clientX - startX;
  const dy = e.clientY - startY;
  draggable.style.transform = `translate(${dx}px, ${dy}px)`;
}
function onMouseUp() {
  document.removeEventListener('mousemove', onMouseMove);
  document.removeEventListener('mouseup', onMouseUp);
}
 

用途

面试这个内容是为了考察候选人对 JavaScript 事件处理和 DOM 操作的掌握程度。实现拖拽功能涉及到对鼠标事件、元素位置计算、性能优化等多方面的知识。在实际生产环境中,这些技能可以应用于实现可视化编辑器、拖拽排序列表、图形界面设计工具等场景。\n

相关问题

🦆
如何使用 CSS 实现元素居中?

可以使用多种方法实现元素居中,例如使用 Flexbox:

 
.container {
  display: flex;
  justify-content: center;
  align-items: center;
}
 

或者使用 Grid 布局:

 
.container {
  display: grid;
  place-items: center;
}
 
🦆
什么是事件委托,如何使用它?

事件委托是一种将事件监听器添加到父元素上,从而通过检测事件目标来处理子元素事件的方法。示例代码如下:

 
document.getElementById('parent').addEventListener('click', (e) => {
  if (e.target && e.target.matches('button.classname')) {
    // 处理点击事件
  }
});
 
🦆
如何防止事件冒泡?

可以使用 'event.stopPropagation()' 方法来阻止事件冒泡。示例代码如下:

 
element.addEventListener('click', (e) => {
  e.stopPropagation();
});
 
🦆
如何实现一个简单的拖放排序列表?

可以使用 HTML5 的拖放 API 实现一个简单的拖放排序列表。示例代码如下:

 
<ul id='sortable'>
  <li draggable='true'>Item 1</li>
  <li draggable='true'>Item 2</li>
  <li draggable='true'>Item 3</li>
</ul>
<script>
const items = document.querySelectorAll('#sortable li');
items.forEach(item => {
  item.addEventListener('dragstart', () => {
    item.classList.add('dragging');
  });
  item.addEventListener('dragend', () => {
    item.classList.remove('dragging');
  });
});
document.querySelector('#sortable').addEventListener('dragover', (e) => {
  e.preventDefault();
  const afterElement = getDragAfterElement(e.clientY);
  const dragging = document.querySelector('.dragging');
  if (afterElement == null) {
    sortable.appendChild(dragging);
  } else {
    sortable.insertBefore(dragging, afterElement);
  }
});
function getDragAfterElement(y) {
  const elements = [...sortable.querySelectorAll('li:not(.dragging)')];
  return elements.reduce((closest, child) => {
    const box = child.getBoundingClientRect();
    const offset = y - box.top - box.height / 2;
    if (offset < 0 && offset > closest.offset) {
      return { offset, element: child };
    } else {
      return closest;
    }
  }, { offset: Number.NEGATIVE_INFINITY }).element;
}
</script>