JS怎么實現(xiàn)懸浮窗拖拽 4行代碼讓元素支持鼠標(biāo)自由拖拽

JS實現(xiàn)懸浮窗拖拽的核心是監(jiān)聽鼠標(biāo)事件并更新位置。1. 優(yōu)化性能:使用transform: translate()替代left和top以啟用gpu加速,并通過節(jié)流函數(shù)限制mousemove觸發(fā)頻率;2. 限制范圍:在mousemove中計算懸浮窗位置,確保不超出屏幕邊界;3. 處理事件沖突:mousedown時阻止冒泡并臨時禁用內(nèi)部元素的pointer-events;4. 吸附邊緣:mouseup時計算最近屏幕邊沿,并使用transition平滑移動到該位置。

JS怎么實現(xiàn)懸浮窗拖拽 4行代碼讓元素支持鼠標(biāo)自由拖拽

JS實現(xiàn)懸浮窗拖拽的核心在于監(jiān)聽鼠標(biāo)事件(mousedown, mousemove, mouseup),并利用這些事件來更新懸浮窗的位置。簡單來說,就是記錄鼠標(biāo)按下時的位置,然后在鼠標(biāo)移動時,計算鼠標(biāo)移動的距離,并將這個距離加到懸浮窗的當(dāng)前位置上。

JS怎么實現(xiàn)懸浮窗拖拽 4行代碼讓元素支持鼠標(biāo)自由拖拽

let dragging = false; let offsetX, offsetY;  element.addEventListener('mousedown', (e) => {   dragging = true;   offsetX = e.clientX - element.offsetLeft;   offsetY = e.clientY - element.offsetTop; });  document.addEventListener('mouseup', () => {   dragging = false; });  document.addEventListener('mousemove', (e) => {   if (!dragging) return;   element.style.left = e.clientX - offsetX + 'px';   element.style.top = e.clientY - offsetY + 'px'; });

如何優(yōu)化拖拽性能,避免卡頓?

JS怎么實現(xiàn)懸浮窗拖拽 4行代碼讓元素支持鼠標(biāo)自由拖拽

優(yōu)化拖拽性能,主要從兩個方面入手:減少重繪和使用硬件加速

JS怎么實現(xiàn)懸浮窗拖拽 4行代碼讓元素支持鼠標(biāo)自由拖拽

  1. 使用transform: translate()代替left和top: transform屬性會觸發(fā)GPU加速,從而減少重繪。修改上面的代碼:
element.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY}px)`; element.style.left = null; // 移除 left 和 top element.style.top = null;
需要注意的是,使用`transform`后,`offsetLeft`和`offsetTop`獲取到的值會是未應(yīng)用`transform`時的位置,因此初始計算`offsetX`和`offsetY`時需要考慮這一點。
  1. 節(jié)流(Throttling): mousemove事件觸發(fā)頻率非常高,可以限制回調(diào)函數(shù)的執(zhí)行頻率。
function throttle(func, delay) {   let timeoutId;   let lastExecTime = 0;    return function(...args) {     const context = this;     const currentTime = new Date().getTime();      if (!timeoutId) {       func.apply(context, args);       lastExecTime = currentTime;       timeoutId = setTimeout(function() {         timeoutId = null;       }, delay);     } else if (currentTime - lastExecTime >= delay) {       func.apply(context, args);       lastExecTime = currentTime;     }   }; }  document.addEventListener('mousemove', throttle((e) => {   if (!dragging) return;   element.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY}px)`; }, 16)); // 16ms 約等于 60FPS

如何限制懸浮窗的拖拽范圍,防止拖出屏幕?

限制拖拽范圍,需要獲取屏幕的寬高,以及懸浮窗自身的寬高,然后在mousemove事件中,判斷懸浮窗的位置是否超出屏幕邊界。

document.addEventListener('mousemove', (e) => {     if (!dragging) return;      const screenWidth = window.innerWidth;     const screenHeight = window.innerHeight;     const elementWidth = element.offsetWidth;     const elementHeight = element.offsetHeight;      let newX = e.clientX - offsetX;     let newY = e.clientY - offsetY;      // 限制左邊界     newX = Math.max(0, newX);     // 限制上邊界     newY = Math.max(0, newY);     // 限制右邊界     newX = Math.min(screenWidth - elementWidth, newX);     // 限制下邊界     newY = Math.min(screenHeight - elementHeight, newY);       element.style.left = newX + 'px';     element.style.top = newY + 'px'; });

如何處理嵌套元素拖拽時的事件沖突?

如果懸浮窗內(nèi)部有可以交互的元素(比如按鈕、輸入框),拖拽時可能會觸發(fā)這些元素的事件,導(dǎo)致拖拽中斷。 解決方法

  1. mousedown事件阻止冒泡: 在懸浮窗的mousedown事件處理函數(shù)中,調(diào)用e.stopPropagation()阻止事件冒泡到內(nèi)部元素。
element.addEventListener('mousedown', (e) => {   dragging = true;   offsetX = e.clientX - element.offsetLeft;   offsetY = e.clientY - element.offsetTop;   e.stopPropagation(); // 阻止事件冒泡 });
  1. css pointer-events: none;: 在拖拽時,可以臨時將懸浮窗內(nèi)部元素的pointer-events設(shè)置為none,禁用它們的鼠標(biāo)事件。 拖拽結(jié)束后再恢復(fù)。
element.addEventListener('mousedown', (e) => {   dragging = true;   offsetX = e.clientX - element.offsetLeft;   offsetY = e.clientY - element.offsetTop;   // 禁用內(nèi)部元素的鼠標(biāo)事件   element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'none'); });  document.addEventListener('mouseup', () => {   dragging = false;   // 恢復(fù)內(nèi)部元素的鼠標(biāo)事件   element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'auto'); });

如何讓懸浮窗在拖拽結(jié)束后自動吸附到屏幕邊緣?

吸附效果可以通過在mouseup事件中計算懸浮窗距離屏幕邊緣的距離,然后使用animate或者transition讓懸浮窗平滑移動到最近的邊緣。

document.addEventListener('mouseup', () => {     dragging = false;      const screenWidth = window.innerWidth;     const screenHeight = window.innerHeight;     const elementWidth = element.offsetWidth;     const elementHeight = element.offsetHeight;      const elementLeft = element.offsetLeft;     const elementTop = element.offsetTop;      const distanceToLeft = elementLeft;     const distanceToTop = elementTop;     const distanceToRight = screenWidth - elementLeft - elementWidth;     const distanceToBottom = screenHeight - elementTop - elementHeight;      let closestEdge = 'left';     let closestDistance = distanceToLeft;      if (distanceToTop < closestDistance) {         closestEdge = 'top';         closestDistance = distanceToTop;     }      if (distanceToRight < closestDistance) {         closestEdge = 'right';         closestDistance = distanceToRight;     }      if (distanceToBottom < closestDistance) {         closestEdge = 'bottom';         closestDistance = distanceToBottom;     }      let targetLeft = elementLeft;     let targetTop = elementTop;      switch (closestEdge) {         case 'left':             targetLeft = 0;             break;         case 'top':             targetTop = 0;             break;         case 'right':             targetLeft = screenWidth - elementWidth;             break;         case 'bottom':             targetTop = screenHeight - elementHeight;             break;     }      element.style.transition = 'all 0.3s ease-in-out'; // 添加過渡效果     element.style.left = targetLeft + 'px';     element.style.top = targetTop + 'px';      element.addEventListener('transitionend', () => {         element.style.transition = 'none'; // 移除過渡效果,避免影響后續(xù)拖拽     }, { once: true }); });

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊6 分享