纯 JS 实现放大缩小拖拽采坑之旅

时间: 2019-10-26阅读: 185标签: 拖拽

前言

最近团队需要做一个智能客服悬浮窗功能,需要支持拖动、放大缩小等功能,因为这个是全局插件,为了兼容性考虑全部使用原生 JS 实现,不引用任何第三方库或者插件。开发过程中遇到的一些问题及解决方法,在这里和大家分享交流一下。

注:下文出现的“采宝”二字,为这个功能的产品名。


先看效果

'实现效果'

看这个效果,相信大部分开发都会觉得实现起来比较容易。在实际开发中,笔者总结了三个主要的坑点,及其解决方案。


三个坑点

  • 拖拽采宝时会导致采宝放大缩小
  • 采宝显示在屏幕边界时被遮挡显示不全
  • 采宝放大和缩小后,位置发生变化


(一)拖拽时会导致采宝放大缩小

我们在操作采宝时,不管是鼠标拖动还是点击放大缩小,我们的事件都需要绑定在采宝头部的图标上,这样我们就需要在图标上同时绑定点击和拖拽事件。但是当我们直接添加 click 事件和 mousedown 事件的时候,我们发现在触发 mousedown 事件的时候,也会去触发 click 事件。这样就会出现在拖动采宝的时候,采宝会放大和缩小。

这个效果是我们不想看到的,所以我们就需要区分开采宝上的 click 事件和 mousedown 事件,想办法使两个事件的触发相互不影响。

所以我们在同一个 DIV 上同时绑定 mousedown 事件和 click 事件,然后通过控制台输出每个事件,查看过程中的每个事件的触发顺序。

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('触发鼠标抬起')
}

然后我们得到的结果是:


通过控制台的输出情况,我们就可以看到鼠标点击后的各个事件触发情况:首先执行的是 mousedown 事件,然后是 mousemove 事件,再然后是 mouseup 事件,最后是 click 事件。

知道了事件的触发顺序,我们就可以通过设置一个变量 isMove 来区分开鼠标的拖动事件和点击事件,每次鼠标按下的时候我们将 isMove 复原,鼠标移动的时候将 isMove 的状态改变。

因为每次触发 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";
}

本文首发于政采云前端团队博客:纯 JS 实现放大缩小拖拽踩坑之旅  

站长推荐

1.阿里云: 本站目前使用的是阿里云主机,安全/可靠/稳定。点击领取2000元代金券、了解最新阿里云产品的各种优惠活动点击进入

2.腾讯云: 提供云服务器、云数据库、云存储、视频与CDN、域名等服务。腾讯云各类产品的最新活动,优惠券领取点击进入

3.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/6621

实现一个美化原生拖拽的draggable-polyfill

在HTML5还未普及之前,实现元素的拖拽还算是一件比较麻烦的事,大概思路就是监听鼠标移动相关事件,下面是伪代码;HTML5新增了拖放draggable标准,拖拽就变得简单了

vue元素拖拽、移动

使用范围:两个元素位置交换,移动元素到指定位置,另外如需有需监听元素的拖拽情况,可调用对应的函数即可。如果不允许拖动到该元素区域内,可在dragover、dragenter中设置dropEffect:none;禁止拖拽。

JS 仿头条App频道编辑功能 (拖拽排序,添加,删减)

由于,项目中使用App混合开发,要实现频道编辑功能;在没找到合适的解决方案的情况下,自己写了这个库;已经在项目中跑了2年多,有不错的可用性;写下这篇文章分享下

实现元素拖拽的两种方式

为将要拖拽的元素设置允许拖拽,并赋予dragstart事件将其id转换成数据保存;为容器添加dragover属性添加事件阻止浏览器默认事件,允许元素放置,并赋予drop事件进行元素的放置。

Element ui表格组件+sortablejs实现行拖拽排序

运营小姐姐说想要可以直接拖拽排序的功能,原来在序号六的广告可能会因为金主爸爸加钱换到序号一的位置,拖拽操作就很方便;实现方式:template部分、script部分

实现平滑过渡的拖拽排序

最近重读Vue官方文档,在列表的排序过渡这一小节,文档提到,<transition-group> 组件有一个特殊的地方,不仅可以实现进入和离开动画,还可以改变定位,官网示例如下

ngDraggable.js_AngularJS之拖拽排序

ngDraggable.js是一款比较简单实用的angularJS拖拽插件,借助于封装好的一些自定义指令,能够快速的进行一些拖拽应用开发。首先先介绍一些基本的概念;ng-drop:是否允许放入拖拽元素

js实现本地图片文件拖拽效果

如何拖拽图片到指定位置,具体方法如下,在从本地上传图片的时候,如果使用拖拽效果,想想应该是更加的高大上,下面直接上js代码

原生js实现拖拽功能

如果要设置物体拖拽,必须使用三个事件,分别是:1、onmousedown:鼠标按下事件2、onmousemove:鼠标移动事件3、onmouseup:鼠标抬开事件

vue模块拖拽实现

正巧在之前面试中遇到问实现拖拽效果。当时面试的时候简单回答了实现的方式与逻辑。现在闲来无事,把这个东西实现了一下。原理很简单,写的很方便。

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!

文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全运营推广