純 JS 實現放大縮小拖拽踩坑之旅

原創不易,但願能關注下咱們,再順手點個贊~~

本文首發於政採雲前端團隊博客: 純 JS 實現放大縮小拖拽踩坑之旅javascript

前言

最近團隊須要作一個智能客服懸浮窗功能,須要支持拖動、放大縮小等功能,由於這個是全局插件,爲了兼容性考慮所有使用原生 JS 實現,不引用任何第三方庫或者插件。開發過程當中遇到的一些問題及解決方法,在這裏和你們分享交流一下。html

注:下文出現的「採寶」二字,爲這個功能的產品名。前端

先看效果

'實現效果'

看這個效果,相信大部分開發都會以爲實現起來比較容易。在實際開發中,筆者總結了三個主要的坑點,及其解決方案。java

三個坑點

  • 拖拽採寶時會致使採寶放大縮小
  • 採寶顯示在屏幕邊界時被遮擋顯示不全
  • 採寶放大和縮小後,位置發生變化

(一)拖拽時會致使採寶放大縮小

咱們在操做採寶時,不論是鼠標拖動仍是點擊放大縮小,咱們的事件都須要綁定在採寶頭部的圖標上,這樣咱們就須要在圖標上同時綁定點擊和拖拽事件。可是當咱們直接添加 click 事件和 mousedown 事件的時候,咱們發如今觸發 mousedown 事件的時候,也會去觸發 click 事件。這樣就會出如今拖動採寶的時候,採寶會放大和縮小。post

點擊盒子

這個效果是咱們不想看到的,因此咱們就須要區分開採寶上的 click 事件和 mousedown 事件,想辦法使兩個事件的觸發相互不影響。性能

因此咱們在同一個 DIV 上同時綁定 mousedown 事件和 click 事件,而後經過控制檯輸出每一個事件,查看過程當中的每一個事件的觸發順序。ui

const moveBox = document.querySelector('.move');
moveBox.onmousedown = function (evt) {
	console.log('觸發鼠標按下')

  moveBox.onmousemove = function (evt) {
    console.log('觸發鼠標拖動')
  }
}

function moveBoxClick(e) {
	console.log('觸發click')
}

moveBox.onmouseup = function () {
	console.log('觸發鼠標擡起')
}
複製代碼

而後咱們獲得的結果是:spa

點擊盒子

經過控制檯的輸出狀況,咱們就能夠看到鼠標點擊後的各個事件觸發狀況:首先執行的是 mousedown 事件,而後是 mousemove 事件,再而後是 mouseup 事件,最後是 click 事件。插件

知道了事件的觸發順序,咱們就能夠經過設置一個變量 isMove 來區分開鼠標的拖動事件和點擊事件,每次鼠標按下的時候咱們將 isMove 復原,鼠標移動的時候將 isMove 的狀態改變。3d

由於每次觸發 click 事件的時候也都會去先去觸發 mousedown 事件,因此咱們在 click 事件裏增長一個判斷,鼠標移動時,不觸發 click 事件。這樣就能夠把 click 事件和 mousedown 事件區分開來,實現 mousedown 和 click 事件的隔離。

click 事件增長判斷

function moveBoxClick(e) {
  // 點擊採寶
  const target = document.querySelector(".move");
  const smallImg = document.querySelector(".small-img");
  const magnifyImg = document.querySelector(".magnify-img");
  // 點擊move盒子
  if (!isMove) {
    if (isBig) {
      smallImg.style.display = "block";
      magnifyImg.style.display = "none";
      target.style.width = "32px";
    } else {
      smallImg.style.display = "none";
      magnifyImg.style.display = "block";
      target.style.width = "130px";
    }
    isBig = !isBig;
  }
}
複製代碼

mousedown 事件重置 isMove 和 mousemove 改變 isMove

let isMove = false; // 是不是拖動
let isBig = false; // 是不是變大的盒子
let isMove = false; // 判斷是否移動採寶
smallImg.onmousedown = magnifyImg.onmousedown = function(evt) {
  isMove = false; // 每次鼠標按下時,重置isMove
  document.onmousemove = function(e) {
    isMove = true; // 每次鼠標移動時,改變isMove
  };
};
複製代碼

經過 isMove 的狀態,咱們就能夠區分開 mousemove 事件和 click 事件,使得咱們在拖動採寶的時候,能夠不去觸發採寶放大縮小。

(二)採寶顯示在屏幕邊界時被遮擋顯示不全

咱們在拖動採寶時,判斷採寶拖動的當前定位座標是否超出了當前顯示屏的高度和寬度,咱們須要限制採寶拖動的最大距離。小採寶在點擊放大時,也須要作一下處理,把採寶所有顯示出來。

拖動時

const moveBox = document.querySelector(".move");
const smallImg = document.querySelector(".move .small-img");
const magnifyImg = document.querySelector(".move .magnify-img");
let isMove = false; // 是不是拖動
let isBig = false; // 是不是變大的盒子

smallImg.onmousedown = magnifyImg.onmousedown = function(evt) {
  // 拖動div盒子
  const clientX = evt.clientX;
  const clientY = evt.clientY;
  const pageX = moveBox.offsetLeft;
  const pageY = moveBox.offsetTop;
  const x = clientX - pageX;
  const y = clientY - pageY;

  document.onmousemove = function(e) {
    // 拖動後採寶的座標
    let _x = e.clientX - x;
    let _y = e.clientY - y;
    const boxWidth = moveBox.offsetWidth;
    const boxHeight = moveBox.offsetHeight;
    if (_x < 0) {
      _x = 0;
    }
    // X座標的最大值
    if (_x > window.screen.width - boxWidth) {
      _x = window.screen.width - boxWidth;
    }
    if (_y < 0) {
      _y = 0;
    }
    // Y座標的最大值
    if (_y > document.documentElement.clientHeight - boxHeight) {
      _y = document.documentElement.clientHeight - boxHeight;
    }
  };
};
複製代碼

小採寶在邊界放大時

// 點擊時,判斷採寶是否超出顯示屏
function autoPotion () {
  let x = moveBox.offsetLeft;
  let y = moveBox.offsetTop;

  if (x < 0) {
  	x = 0;
  } else if (x > document.documentElement.clientWidth - moveBox.offsetWidth) {
  	x = document.documentElement.clientWidth - moveBox.offsetWidth;
  }

  if (y < 0) {
  	y = 0;
  } else if (y > document.documentElement.clientHeight - moveBox.offsetHeight) {
  	y = document.documentElement.clientHeight - moveBox.offsetHeight;
  }

  moveBox.style.left = x + "px";
  moveBox.style.top = y + "px";
}
複製代碼

效果以下

'放大縮小'

(三)採寶放大和縮小後,位置發生變化

經過上圖,咱們能夠看到,當小採寶處在顯示屏邊界時,點擊放大後再點擊縮小,咱們發現採寶的位置發生了變化。這個是由於採寶是根據左上角的座標來定位的,當小採寶移動到右下角時,點擊放大之後,採寶左上角的座標發生了變化,這樣就使得采寶在放大縮小時,位置在發生變化。因此,咱們在採寶移動完成時須要記錄採寶左上角的座標,在點擊時,須要將採寶上次移動完成的座標從新賦值給採寶,這樣就使得采寶在放大縮小時,位置不會發生變化。

'點擊位置變化'

這樣,咱們把每次 mouseup 事件的時候記錄下採寶的位置,這樣咱們解決了採寶放大縮小時位置發生變化的問題。

完整的代碼

HTML:

<div class="box">
  <div class="move">
    <img onclick="moveBoxClick()" class="small-img" draggable="false" src="https://zcy-cdn.oss-cn-shanghai.aliyuncs.com/f2e-assets/103bbf76-6248-421c-a3d6-28a525c459db.png" alt="" />
    <img onclick="moveBoxClick()" class="magnify-img" draggable="false" src="https://zcy-cdn.oss-cn-shanghai.aliyuncs.com/f2e-assets/90e26f49-9824-4443-b4aa-8aa64a3c8690.png" alt="" />
    <div class="content"></div>
  </div>
</div>
複製代碼

JavaScript

const moveBox = document.querySelector(".move");
const smallImg = document.querySelector(".move .small-img");
const magnifyImg = document.querySelector(".move .magnify-img");
var initX = 0; // 記錄小採寶的x座標
var initY = 0; // 記錄小採寶的y座標
let isMove = false; // 是不是拖動
let isBig = false; // 是不是變大的盒子
    
smallImg.onmousedown = magnifyImg.onmousedown = function(evt) {
// 拖動div盒子
const clientX = evt.clientX;
const clientY = evt.clientY;
const pageX = moveBox.offsetLeft;
const pageY = moveBox.offsetTop;
const x = clientX - pageX;
const y = clientY - pageY;

isMove = false;

document.onmousemove = function(e) {
    const boxWidth = moveBox.offsetWidth;
    const boxHeight = moveBox.offsetHeight;
    let _x = e.clientX - x;
    let _y = e.clientY - y;
    if (_x < 0) {
        _x = 0;
    }
    if (_x > window.screen.width - boxWidth) {
        _x = window.screen.width - boxWidth;
    }
    if (_y < 0) {
         _y = 0;
    }
    if (_y > document.documentElement.clientHeight - boxHeight) {
        _y = document.documentElement.clientHeight - boxHeight;
    }

    if (isBig) {
        initX = _x;
        initY = _y;
    }

    moveBox.style.left = _x + "px";
    moveBox.style.top = _y + "px";

    isMove = true;
    };
};


document.onmouseup = function() {
    if (isMove) {
        initX = moveBox.offsetLeft;
        initY = moveBox.offsetTop;
        }
      document.onmousemove = null;
};

function moveBoxClick(e) {
    const target = document.querySelector(".move");
    const smallImg = document.querySelector(".small-img");
    const magnifyImg = document.querySelector(".magnify-img");
    // 點擊move盒子
    if (!isMove) {
        if (isBig) {
          smallImg.style.display = "block";
          magnifyImg.style.display = "none";
          target.style.width = "32px";
          target.style.left = initX + 'px';
          target.style.top = initY + 'px';
        } else {
          smallImg.style.display = "none";
          magnifyImg.style.display = "block";
          target.style.width = "130px";
        }
        isBig = !isBig;

        setTimeout(() => {
          autoPotion();
        }, 100)
    }
}

// 點擊時,判斷採寶是否超出顯示屏
function autoPotion () {
    let x = moveBox.offsetLeft;
    let y = moveBox.offsetTop;

    if (x < 0) {
        x = 0;
    } else if (x > document.documentElement.clientWidth - moveBox.offsetWidth) {
        x = document.documentElement.clientWidth - moveBox.offsetWidth;
    }

    if (y < 0) {
        y = 0;
    } else if (y > document.documentElement.clientHeight - moveBox.offsetHeight) {
        y = document.documentElement.clientHeight - moveBox.offsetHeight;
    }

    moveBox.style.left = x + "px";
    moveBox.style.top = y + "px";
}
複製代碼

總結

經過開發一個很小的功能點,引出了不少須要處理的細節問題,提升用戶體驗。針對這些問題的解決方案,各位看官若是有更好的解決方案,歡迎留言討論。

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「 5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

推薦閱讀

1024 鉅獻!!一文看盡前端過去一年的精華沉澱(700 篇好文大彙總)

多是最全的 「文本溢出截斷省略」 方案合集

乾坤大挪移!React 也能 「用上」 computed 屬性

相關文章
相關標籤/搜索