使用原生js编写一个简单的框选功能方法

今天我们来聊一下怎么使用原生javascript编写一个简单的框选功能。

需求描述

  • 鼠标左键按下不放,移动鼠标出现矩形选框;
  • 鼠标左键松开,根据上边出现的矩形选框统计选框范围内的DOM元素;

嗯...上边的功能描述看着是挺简单的,但实现起来也还是会有些地方需要斟酌思考的。比如,如果我们的框选范围不是document.body,而是某一个div里边进行框选呢?而现实开发过程中,我们会遇上的应该就是第二种情况。

点击查看完整的源码

怎么实现

二话不说,咱们动手写代码吧!因为更好的兼容性,这里就避免了一些ES6的语法,如果是用的其他框架来写的话,代码上相应的也要做一些调整。

<head>
<style>
.fileDiv {
 display: inline-block;
 width: 100px;
 height: 100px;
 margin: 24px;
 background-color: blue;
}
</style>
</head>
<body>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
 <div class="fileDiv"></div>
</body>

添加鼠标事件监听

由于js自身并没有带有鼠标点击按住不放这样子的事件,这里我们不仅需要检测鼠标左键点击按下,还要加一个定时器来检测鼠标是否按住不放了。

<script>
 (function () {
  // 定时器id
  var mouseStopId;
  // 是否开启框选功能
  var mouseOn = false;
  // 用来存放鼠标点击初始位置
  var startX = 0;
  var startY = 0;
  // 添加鼠标按下监听事件
  document.body.addEventListener('mousedown', function (e) {
   // 阻止事件冒泡
   clearEventBubble(e);
   // 判断是否为鼠标左键被按下
   if (e.buttons !== 1 || e.which !== 1) return;

   mouseStopId = setTimeout(function () {
    mouseOn = true;
    startX = e.clientX;
    startY = e.clientY;
   }, 300); // 间隔300毫秒后执行,判定这时候鼠标左键被按住不放
  });

  // 添加鼠标移动事件监听
  document.body.addEventListener('mousemove', function (e) {
   // 如果并非框选开启,退出
   if (!mouseOn) return;
   // 阻止事件冒泡
   clearEventBubble(e);
   // 处理鼠标移动
   // codes
  });

  // 添加鼠标点击松开事件监听
  document.body.addEventListener('mouseup', function (e) {
   // 阻止事件冒泡
   clearEventBubble(e);
   // 处理鼠标点击松开
   // codes
  });

  function clearEventBubble (e) {
   if (e.stopPropagation) e.stopPropagation();
   else e.cancelBubble = true;

   if (e.preventDefault) e.preventDefault();
   else e.returnValue = false;
  }
 })();
</script>

添加框选可视化元素

框选可视化元素示意图

我们有了事件监听还不够,为了更好的交互效果,我们需要一个随时跟随着鼠标移动的框选框元素,用于让用户随时感知框选范围。

<script>
 (function () {
  var mouseStopId;
  var mouseOn = false;
  var startX = 0;
  var startY = 0;
  document.body.addEventListener('mousedown', function (e) {
   clearEventBubble(e);
   if (e.buttons !== 1 || e.which !== 1) return;

   mouseStopId = setTimeout(function () {
    mouseOn = true;
    startX = e.clientX;
    startY = e.clientY;
    // 创建一个框选元素
    var selDiv = document.createElement('div');
    // 给框选元素添加css样式,这里使用绝对定位
    selDiv.style.cssText = 'position:absolute;width:0;height:0;margin:0;padding:0;border:1px dashed #eee;background-color:#aaa;z-index:1000;opacity:0.6;display:none;';
    // 添加id
    selDiv.id = 'selectDiv';
    document.body.appendChild(selDiv);
    // 根据起始位置,添加定位
    selDiv.style.left = startX + 'px';
    selDiv.style.top = startY + 'px';
   }, 300);
  });

  document.body.addEventListener('mousemove', function (e) {
   if (!mouseOn) return;
   clearEventBubble(e);
   // 获取当前坐标
   var _x = e.clientX;
   var _y = e.clientY;
   // 根据坐标给选框修改样式
   var selDiv = document.getElementById('selectDiv');
   selDiv.style.display = 'block';
   selDiv.style.left = Math.min(_x, startX) + 'px';
   selDiv.style.top = Math.min(_y, startY) + 'px';
   selDiv.style.width = Math.abs(_x - startX) + 'px';
   selDiv.style.height = Math.abs(_y - startY) + 'px';
   // 如果需要更直观一点的话,我们还可以在这里进行对框选元素覆盖到的元素进行修改被框选样式的修改。
  });

  document.body.addEventListener('mouseup', function (e) {
   clearEventBubble(e);
  });

  function clearEventBubble (e) {
   if (e.stopPropagation) e.stopPropagation();
   else e.cancelBubble = true;

   if (e.preventDefault) e.preventDefault();
   else e.returnValue = false;
  }
 })();
</script>

添加鼠标松开事件监听

元素是否被选中示意图

我们没有在鼠标移动的时候去实时统计被框选到的DOM元素,如果需要实时统计或者实时修改被选择的DOM元素的样式,以便更准确的让用户感知到被框选的内容的话,可以选择在mousemove事件里边去实现以下代码:

<script>
 (function () {
  var mouseStopId;
  var mouseOn = false;
  var startX = 0;
  var startY = 0;
  document.onmousedown = function (e) {
   clearEventBubble(e);
   if (e.buttons !== 1 || e.which !== 1) return;

   mouseStopId = setTimeout(function () {
    mouseOn = true;
    startX = e.clientX;
    startY = e.clientY;
    var selDiv = document.createElement('div');
    selDiv.style.cssText = 'position:absolute;width:0;height:0;margin:0;padding:0;border:1px dashed #eee;background-color:#aaa;z-index:1000;opacity:0.6;display:none;';
    selDiv.id = 'selectDiv';
    document.body.appendChild(selDiv);
    selDiv.style.left = startX + 'px';
    selDiv.style.top = startY + 'px';
   }, 300);
  }

  document.onmousemove = function (e) {
   if (!mouseOn) return;
   clearEventBubble(e);
   var _x = e.clientX;
   var _y = e.clientY;
   var selDiv = document.getElementById('selectDiv');
   selDiv.style.display = 'block';
   selDiv.style.left = Math.min(_x, startX) + 'px';
   selDiv.style.top = Math.min(_y, startY) + 'px';
   selDiv.style.width = Math.abs(_x - startX) + 'px';
   selDiv.style.height = Math.abs(_y - startY) + 'px';
  };

  // 添加鼠标松开事件监听
  document.onmouseup = function (e) {
   if (!mouseOn) return;
   clearEventBubble(e);
   var selDiv = document.getElementById('selectDiv');
   var fileDivs = document.getElementsByClassName('fileDiv');
   var selectedEls = [];
   // 获取参数
   var l = selDiv.offsetLeft;
   var t = selDiv.offsetTop;
   var w = selDiv.offsetWidth;
   var h = selDiv.offsetHeight;
   for (var i = 0; i < fileDivs.length; i++) {
    var sl = fileDivs[i].offsetWidth + fileDivs[i].offsetLeft;
    var st = fileDivs[i].offsetHeight + fileDivs[i].offsetTop;

    if (sl > l && st > t && fileDivs[i].offsetLeft < l + w && fileDivs[i].offsetTop < t + h) {
     // 该DOM元素被选中,进行处理
     selectedEls.push(fileDivs[i]);
    }
   }
   // 打印被选中DOM元素
   console.log(selectedEls);
   // 恢复参数
   selDiv.style.display = 'none';
   mouseOn = false;
  };

  function clearEventBubble (e) {
   if (e.stopPropagation) e.stopPropagation();
   else e.cancelBubble = true;

   if (e.preventDefault) e.preventDefault();
   else e.returnValue = false;
  }
 })();
</script>

这里判断一个元素是否被选中采用的判断条件是:

  • 该DOM元素的最右边(fileDiv[i].offsetLeft + fileDiv[i].offsetWidth)是否要比选框元素最左边(selDiv.offsetLeft)的位置要小;
  • 该DOM元素的最下边(fileDiv[i].offsetTop + fileDiv[i].offsetHeight)是否要比选框元素的最上边(selDiv.offsetTop)的位置要大;
  • 该DOM元素的最左边(fileDiv[i].offsetLeft)是否要比选框元素的最后边(selDiv.offsetLeft + selDiv.offsetWidth)的位置数值要小;
  • 该DOM元素的最上边(fileDiv[i].offsetTop)是否要比选框元素的最下边(selDiv.offsetTop + selDiv.offsetHeight)的位置数值要小;

满足了以上四个条件,即可判别为该DOM元素被选中了。

实际应用

上边的例子,举得有些过于简单了。实际的开发当中,框选的范围往往不可能是整个document.body,而是某一个具体的有特定宽度跟高度限制的元素。这个时候,就还需要考虑这个框选容器元素造成的定位偏差,以及容器内元素过多,出现滚动条的情况了。

乍一看,上边的情况需要考虑的因素多了不少,比较容易乱。我这里采用的方法是修改坐标系的方式来实现上边描述的功能。上文我们已经实现了在document.body整个页面左上角顶点作为坐标原点来实现框选功能,这时候我们需要修改坐标原点为框选容器的左上角顶点作为坐标原点即可。

换言之,就是修改mousedown跟mousemove事件时,初始位置由原来的e.clientX跟e.clientY修改为e.clientX - selectContaienr.offsetLeft + selectContainer.scrollLeft跟e.clientY - selectContainer.offsetTop + selectContainer.scrollTop。

坐标更改shi'yi'tu

<html>
 <head>
  <title>region</title>
  <style>
   body {
    margin: 0;
    padding: 0;
   }
   #selectContainer {
    position: relative;
    width: 400px; /* 演示宽高与位置 */
    height: 400px;
    top: 200px;
    left: 200px;
    border: 1px solid #eee;
    overflow: hidden;
    overflow-y: auto;
   }
   .fileDiv {
    display: inline-block;
    width: 100px;
    height: 100px;
    margin: 24px;
    background-color: #0082CC;
   }
  </style>
 </head>
 <body>
  <div id="selectContainer">
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
   <div class="fileDiv"></div>
  </div>
 </body>
</html>
<script>
 (function () {
  var mouseStopId;
  var mouseOn = false;
  var startX = 0;
  var startY = 0;
  document.onmousedown = function (e) {
   clearEventBubble(e);
   if (e.buttons !== 1 || e.which !== 1) return;

   mouseStopId = setTimeout(function () {
    mouseOn = true;
    // 获取容器元素
    var selectContainer = document.getElementById('selectContainer');
    // 调整坐标原点为容器左上角
    startX = e.clientX - selectContainer.offsetLeft + selectContainer.scrollLeft;
    startY = e.clientY - selectContainer.offsetTop + selectContainer.scrollTop;
    var selDiv = document.createElement('div');
    selDiv.style.cssText = 'position:absolute;width:0;height:0;margin:0;padding:0;border:1px dashed #eee;background-color:#aaa;z-index:1000;opacity:0.6;display:none;';
    selDiv.id = 'selectDiv';
    // 添加框选元素到容器内
    document.getElementById('selectContainer').appendChild(selDiv);
    selDiv.style.left = startX + 'px';
    selDiv.style.top = startY + 'px';
   }, 300);
  }

  document.onmousemove = function (e) {
   if (!mouseOn) return;
   clearEventBubble(e);
   var selectContainer = document.getElementById('selectContainer');
   var _x = e.clientX - selectContainer.offsetLeft + selectContainer.scrollLeft;
   var _y = e.clientY - selectContainer.offsetTop + selectContainer.scrollTop;
   var _H = selectContainer.clientHeight;
   // 鼠标移动超出容器内部,进行相应的处理
   // 向下拖拽
   if (_y >= _H && selectContainer.scrollTop <= _H) {
    selectContainer.scrollTop += _y - _H;
   }
   // 向上拖拽
   if (e.clientY <= selectContainer.offsetTop && selectContainer.scrollTop > 0) {
    selectContainer.scrollTop = Math.abs(e.clientY - selectContainer.offsetTop);
   }
   var selDiv = document.getElementById('selectDiv');
   selDiv.style.display = 'block';
   selDiv.style.left = Math.min(_x, startX) + 'px';
   selDiv.style.top = Math.min(_y, startY) + 'px';
   selDiv.style.width = Math.abs(_x - startX) + 'px';
   selDiv.style.height = Math.abs(_y - startY) + 'px';
  };

  document.onmouseup = function (e) {
   if (!mouseOn) return;
   clearEventBubble(e);
   var selDiv = document.getElementById('selectDiv');
   var fileDivs = document.getElementsByClassName('fileDiv');
   var selectedEls = [];
   var l = selDiv.offsetLeft;
   var t = selDiv.offsetTop;
   var w = selDiv.offsetWidth;
   var h = selDiv.offsetHeight;
   for (var i = 0; i < fileDivs.length; i++) {
    var sl = fileDivs[i].offsetWidth + fileDivs[i].offsetLeft;
    var st = fileDivs[i].offsetHeight + fileDivs[i].offsetTop;

    if (sl > l && st > t && fileDivs[i].offsetLeft < l + w && fileDivs[i].offsetTop < t + h) {
     selectedEls.push(fileDivs[i]);
    }
   }
   console.log(selectedEls);
   selDiv.style.display = 'none';
   mouseOn = false;
  };

  function clearEventBubble (e) {
   if (e.stopPropagation) e.stopPropagation();
   else e.cancelBubble = true;

   if (e.preventDefault) e.preventDefault();
   else e.returnValue = false;
  }
 })();
</script>

使用前端框架

上边的代码,我们只是在一个html文件里边实现了框选的功能。很多时候,我们会使用一些前端框架来编写框选的功能(例如vue.js,angular,react,polymer之类的前端框架)。这个时候,我们可以利用框架自身的生命周期的函数,添加对应的监听事件,然后在mouseup事件里移除掉上边这些事件监听,以减少不必要的资源消耗。而且,很多时候,组件化的使用,使得被框选的元素,往往也是一个可重复利用的小组件,也是需要根据相应的框架的对应的途径获取到对应的DOM元素来获取其属性。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 得到文本框选中的文字,动态插入文字的js代码

    复制代码 代码如下: <script language="javascript" src="js/settags.js"></script>  function AppTag(tagcode)  {   document.PostMessage.Message.value += tagcode;  } function InsertTag(tagbegin,tagend)  {   if ((document.selection)&&

  • javascript 复选框选择/全选后特效

    运行效果 @charset "utf-8"; .content form { margin:0; } table { border:1px solid #CCC; background:#E4E4E4; } td { border-top:1px solid #CCC; background:#FFF; font-size:12px; } th,td,.quantity { text-align:center; font-family:Arial, Helvetica, sans-se

  • JS实现鼠标框选效果完整实例

    本文实例讲述了JS实现鼠标框选效果的方法.分享给大家供大家参考,具体如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; char

  • js实现文本框选中的方法

    本文实例讲述了js实现文本框选中的方法.分享给大家供大家参考.具体如下: 这段javascript代码可解决文本框获得焦点,即使得文本框的内容被选中. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www

  • 用js代码改变单选框选中状态的简单实例

    今天突然有一个需求要用到,使用js代码改变单选框的选中状态.当时想也不想直接 复制代码 代码如下: function doGender(gender) { if (gender == "男") { gel("radionan").style.checked = "checked"; } else { gel("radionv").style.checked = "checked"; }} function

  • 用javascript实现鼠标框选

    起初是打算兼容 Netscape 和 FireFox 等浏览器的,但这些浏览器中不支持 style.pixelLeft,得使用 style.left 之类的(style.pixelLeft 为数字无单位,style.left 为文本有单位),实际使用中发现效果很不好,有延迟状,所以还是使用 style.pixelLeft,缺点是仅支持 IE 系列浏览器. 鼠标框选框 document.body.clientWidth || event.clientY>document.body.clientHe

  • Jquery和angularjs获取check框选中的值的方法汇总

    在我们平常的开发中,有时候会需要获取一下check框选中的值,以及check框选中行的所有信息.这个时候有一个小技巧那就是我们可以把要获取的信息全部放到check框的值里面,这样我们可以获取check框选中值的时候也就相当于获取了当前行的信息. 复制代码 代码如下: <td><input class="states" type="checkbox"  name="orders"  value="{{e.merchant

  • javascript实现复选框选中属性

    熟悉web前端开发的人都知道,判断复选框是否选中是经常做的事情,判断的方法很多,但是开发过程中常常忽略了这些方法的兼容性,而是实现效果就好了.博主之前用户不少方法,经常Google到一些这个不好那个不好的文章,到后面自己都混乱了.今天偶然看到一篇外国的博客,觉得讲解的很不错,打算翻译成中文,并加上了一些自己的见解. 如果你从事web开发并且在你开发的网页中有复选框,你可能需要判断当前该复选框是否选中,进而执行一些条件语句.有很多种方法来判断一个复选框是否选中. 让我们先来看看原生的javascr

  • 使用原生js编写一个简单的框选功能方法

    今天我们来聊一下怎么使用原生javascript编写一个简单的框选功能. 需求描述 鼠标左键按下不放,移动鼠标出现矩形选框: 鼠标左键松开,根据上边出现的矩形选框统计选框范围内的DOM元素: 嗯...上边的功能描述看着是挺简单的,但实现起来也还是会有些地方需要斟酌思考的.比如,如果我们的框选范围不是document.body,而是某一个div里边进行框选呢?而现实开发过程中,我们会遇上的应该就是第二种情况. 点击查看完整的源码 怎么实现 二话不说,咱们动手写代码吧!因为更好的兼容性,这里就避免了

  • 原生JS实现的简单轮播图功能【适合新手】

    本文实例讲述了原生JS实现的简单轮播图功能.分享给大家供大家参考,具体如下: 经过几天的努力,终于攻克了这一难题,于是迫不及待的想要分享给大家,编写之前,我也看了不少其他博主的博客,大多是用偏移量写的,对新手来说,还是有些难以理解,虽然可能实现的需求不一样,但我想先从简入手,所以自己查阅资料,修改bug,终于完成.话不多说,上代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q

  • 原生js添加一个或多个类名的方法分析

    本文实例讲述了原生js添加一个或多个类名的方法.分享给大家供大家参考,具体如下: 好吧今天写个js,不知道怎么添加类名了 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title></title> <style type="text/css" media="screen"

  • vue.js做一个简单的编辑菜谱功能

    先给大家展示下效果图,如果感觉不错,请参考实现代码 1.先获取门店下的所有菜品类型.菜品名称.菜品id(list),也就是最大数据量 this.$http.post(ceshiApi+'getCyFoodAndFoodTypeForShopId',{shopId:this.shopId},{emulateJSON:true,credentials: true}).then(function(res){ if(res.data.type=='success'){ this.foodList = r

  • js编写一个简单的产品放大效果代码

    购物网站的产品页经常会放有一个产品展示图区.该图区有一个功能就是产品图的放大功能,移动左侧的焦点区域,可以放大细节部分观看,详情如下图.实现该功能的方法也非常简单. 实验:制作产品焦点放大图. 所需技能: 1.基本的获取页面元素的方法: 2.几个简单的事件: 3.会使用dom设置元素的属性: 案例原理: 1.焦点框的跟随鼠标事件: 2.焦点框的移动区域规定: 3.大盒子内容的显示: 适合对象:js初学者 ----------------------------------------------

  • JavaScript制作简单的框选图表

    故事背景:这几天遇到一个客户,是做会议记录的,每次会议过程中,都会有特定设备记录下讲话人的位置以角度值显示.他给我角度值,让我给他做一个图表来展示每个人的一个大概位置. 客户想到的是用 Echarts 图表来做,我首先想到的也是用 Echarts ,但是思考了他的要求以后,发现就一个简单的框选图表用 Echarts 来做是不是大材小用了,而且还要导入那么多的没用的代码. 于是我想到了用 canvas 画布来仿着做,但又考虑了一下, canvas 操作起来不顺手:究竟可不可以用普通的css结合 j

  • Angular实现一个简单的多选复选框的弹出框指令实例

    之前的文章有写过包含树结构下拉框的. 要实现一个包含多个复选框的下拉框该如何做呢? 先上个效果图吧: 代码: <!DOCTYPE html> <html ng-app="app"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" type="text/css"

  • 利用原生js和jQuery实现单选框的勾选和取消操作的方法

    根据以下的Demo,大概就可以看的明白 Demo: <html> <head> <script src='jquery-1.9.1.min.js'></script> <script> window.onload = function(){ var dom_a = document.getElementById('a1'); var dom_b = document.getElementById("b1"); //alert(

  • js编写当天简单日历效果【实现代码】

    之前一直很想用javascript写一个日历,但是因为完全没有好的思路, 所以迟迟没有尝试.最近在网上刚好看到用javascript编写的简单日历的例子,代码量虽然不大, 但是我觉得很好地阐述了js日历的实现原理.自己也尝试着做了一下,收获蛮大,掌握了基本的实现原理后,再想增加更多的功能,完全就可以自由发挥了,先在这里分享一下吧,有兴趣的可以试试! 一.表格行数问题 既然要显示日期表格的话,首先得知道这个表格有多少行多少列,列数是已经确定的,从星期天(日历上第1列是星期天)到星期六一共7列.要解

  • 原生js编写焦点图效果

    自己用原生js写的简单焦点图,算是对原生js初步运用. html部分 <div class="focusPicture"> <section class="mPhoto" id="mPhoto"> <img src="images/20161021/b_5809b93b66d18.jpg"/> </section><!-- 主要的大图 --> <section

随机推荐