vue仿windows实现单选、多选、连选、框选效果-阿里云开发者社区

开发者社区> Villin> 正文

vue仿windows实现单选、多选、连选、框选效果

简介: vue实现单机单选,按Ctrl+多选,按shift+连选,鼠标框选效果。
+关注继续查看

最近博主公司出现一个需求,做一个云盘项目(或许未来的你能用得上),让仿照windows系统实现多选功能,虽然项目上就放一个Ctrl+多选,但我一开心,历经两天,其他的也都实现了。查阅各种资料,某度上天花乱坠,但很多方法还是严重不合格。所以将个人研究的公布出来,大家喜欢可借鉴,研发不易,希望给个赞👍。

框选效果
image.png
Ctrl 多选效果
image.png
Shift 连选效果
image.png

300行代码实现所有功能,简单做了一些注释,将思路给大家,专研一下,能看懂。需要注意的是,毕竟html页面实现,性能上还需继续优化。

实现功能:

1、点击Ctrl ,可多选,可取消选过文件。
2、点击shift,点击两个,中间可连选。
3、按下鼠标移动,可框选。
4、单机,可单选。
5、按下空白处,取消所有选项。

研发思路:

1、所有实现中,都使用列表中的index作为选中标识。并放进一个独立数组中,我起名为 selectList 。
2、所有list列表中添加一个标识,作为是否选中的样式判断,这样做是最方便的,标识没有自己手动添加,或让后端给加入一个标识字段,对他们来说很简单。
3、每次选中后,都将遍历所有list列表,将符合条件的index对象中,标识变为true,否则为false。
4、监听键盘事件,获取到shift 和ctrl的keycode,由于博主mac本,所以需要多加一个command 。

一、多选思路:
点击时判断 selectList 数组中有没有重复index,有则删除,没有则添加。

二、连选思路:
a、连选是随意点击两个li,并将中间的全部选中。
b、判断 selectList.length 是否大于1,判断最低是两个数,可以实现连选。
c、用户可能从前往后选,也可能从后往前选,所以判断 selectList 的第一个index 和最后一个 index 的大小,并从小到大排序,做一个for循环,将中间index都填入到 selectList 中,遍历点亮数组。

注意:本次实现,将ctrl点击的一同带入,所以和 windows 系统略有差异。

三、框选思路:
a、css禁用浏览器默认选择效果,本次用的fixed定位。
b、js实现选框的效果。
c、判断框选的覆盖范围,与list的交集,拿到交集的index。

框选避坑处:
a、现实开发中,最外层盒子和ul的高度未必统一,以及ul 的高度未必100%,我们可能回根据最外层盒子做框选效果,所以建议用fixed定位。
b、获取 li 坐标。因为所有 li 的盒子的坐标,是针对父盒子做的定位,并不是针对窗口坐标位置,所以需要加上父盒子针对窗口的坐标高度,就是li 针对窗口的坐标。
c、页面滚动时,父盒子针对窗口的坐标高度不会更新,所以监听滚动时间,实时更新变化。
b、注意性能问题,本次没有实时更新 li 坐标,而是每个 li 直接加上父盒子针对窗口的坐标高度。

代码展示:

<template>
  <div id='se'
       @mousedown.stop="handleBox">
    <ul id="ul">
      <li v-for="(item,index) in list"
          :key="index"
          @click.stop="handleClick(index)"
          :class="item.isS?'isS':''"
          class="li">{{item.name+ (index+1)}}</li>
    </ul>
    <div v-show="isShowSeBox"
         id="selection"><span>VX:VillinWeChat</span><span>🙏谢谢送个👍</span></div>
  </div>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      list: [
        {
          name: '列表',
          id: 1,
          isS: false,
        },
        {
          name: '列表',
          id: 2,
          isS: false,
        },
        {
          name: '列表',
          id: 3,
          isS: false,
        },
        {
          name: '列表',
          id: 4,
          isS: false,
        },
        {
          name: '列表',
          id: 5,
          isS: false,
        },
        {
          name: '列表',
          id: 6,
          isS: false,
        }, {
          name: '列表',
          id: 7,
          isS: false,
        }, {
          name: '列表',
          id: 8,
          isS: false,
        }, {
          name: '列表',
          id: 9,
          isS: false,
        }, {
          name: '列表',
          id: 10,
          isS: false,
        },
        {
          name: '列表',
          id: 11,
          isS: false,
        },
        {
          name: '列表',
          id: 12,
          isS: false,
        },
      ],
      isshift: false, // 是否按下 shift
      isctrl: false, // 是否按下 ctrl
      isonkeydown: false, // 是否按下键盘
      selectBox: null, // 框选盒子
      isShowSeBox: false, // 框选是否显示div
      selectList: [], // 每次点击,push进去点击的index
      lipL: 0,  // 框选li的父盒子针对于窗口的left
      lipT: 0, // 框选li的父盒子针对于窗口的top
    };
  },
  mounted() {
    this.selectBox = document.getElementById("selection");
    this.parentBox = document.getElementById("se");
    this.handleScroll()
  },
  computed: {},
  watch: {},
  methods: {
    handleBox(even) {
      if (!this.isctrl && !this.isshift && this.selectList.length != 0) {
        this.selectList = []
        this.loop()
      }
      // 按下时的坐标
      this.downX = even.clientX
      this.downY = even.clientY
      this.selectBox.style.left = this.downX + "px";
      this.selectBox.style.top = this.downY + "px";
      document.body.addEventListener('mousemove', this.moveselecttion)
      document.body.addEventListener('mouseup', this.mouseupSelection)
      window.addEventListener("scroll", this.handleScroll)
      window.addEventListener('keydown', this.clickKeydown)
      window.addEventListener('keyup', this.clickKeyup)
    },
    // 页面滚动  滚动时,实时更新父盒子横纵坐标
    handleScroll() {
      //获取li父元素的纵坐标(相对于窗口)
      this.lipT = this.parentBox.getBoundingClientRect().top
      //获取li父元素的横坐标(相对于窗口)
      this.lipL = this.parentBox.getBoundingClientRect().left
    },
    // 键盘按下
    clickKeydown(e) {
      switch (e.keyCode) {
        case 16:
          this.isshift = true;
          break;
        case 17: // window 键盘
          this.isctrl = true;
          break;
        case 91:  // mac command  按键
          this.isctrl = true;
          break;
      }
    },
    // 键盘松开
    clickKeyup(e) {
      this.isshift = false
      this.isctrl = false
    },
    handleClick(index) {
      // shift 连选
      if (this.isshift) {
        if (this.selectList.indexOf(index) == -1) {
          this.selectList.push(index)
        }
        if (this.selectList.length > 1) {
          // 按 shift 连选,通过判断第一次点击index和最后一次index,中间全部选中
          let le = this.selectList.length
          var min = Math.min(this.selectList[0], this.selectList[le - 1])
          var max = Math.max(this.selectList[0], this.selectList[le - 1])
          this.selectList = []  // 防止最初始的两次点击,多处的两个index
          for (let i = min; i <= max; i++) {
            this.selectList.push(i)
          }
        }
        this.loop()
      }
      // ctrl 多选 可重复单机取消
      else if (this.isctrl) {
        if (this.selectList.indexOf(index) == -1) {
          this.selectList.push(index)
        } else {
          // 此处判断是否存在高亮index,如果存在,再次点击后,则删除此index,达到二次点击取消选区效果
          this.selectList.splice(this.selectList.indexOf(index), 1)
        }
        this.loop()
      }
      // 单选
      else {
        this.selectList = []
        this.selectList.push(index)
        this.loop()
      }
    },

    // 鼠标移动事件
    moveselecttion(even) {
      this.isShowSeBox = true;
      this.selectBox.style.left = Math.min(even.clientX, this.downX) + "px";
      this.selectBox.style.top = Math.min(even.clientY, this.downY) + "px";
      this.selectBox.style.width = Math.abs(this.downX - even.clientX) + "px";
      this.selectBox.style.height = Math.abs(this.downY - even.clientY) + "px";
      this.covered()
    },
    // 鼠标松开事件
    mouseupSelection(even) {
      this.isShowSeBox = false;
      this.selectBox.style.width = 0 + "px";
      this.selectBox.style.height = 0 + "px";
      document.body.removeEventListener('mousemove', this.moveselecttion)
      document.body.removeEventListener('mouseup', this.mouseupSelection)
    },
    // 是否被覆盖
    covered() {
      let li = document.getElementsByClassName('li')
      if (!this.isctrl && !this.isctrl) {
        this.selectList = []
      }
      let l = parseInt(this.selectBox.style.left)
      let t = parseInt(this.selectBox.style.top)
      let w = parseInt(this.selectBox.style.width)
      let h = parseInt(this.selectBox.style.height)
      for (let i = 0; i < li.length; i++) {
        if (this.selectList.indexOf(i) == -1) {
          //  子元素是相对父元素定位的,所以父元素有定位高度,offsetLeft 和 offsetTop不准
          let sl = li[i].offsetWidth + li[i].offsetLeft + this.lipL;
          let st = li[i].offsetHeight + li[i].offsetTop + this.lipT;
          if (sl > l && st > t && li[i].offsetLeft + this.lipL < l + w && li[i].offsetTop + this.lipT < t + h) {
            this.selectList.push(i)
          }
          this.loop()
        }

      }
    },
    // 循环遍历,在这里,可以拿到所选的item
    loop() {
      this.list.forEach((e, index) => {
        e.isS = false
        this.selectList.filter(item => {
          if (item == index) {
            e.isS = true
          }
        })
      });
    },
  },
  destroyed() {
    document.body.removeEventListener('mousemove', this.moveselecttion)
    document.body.removeEventListener('mouseup', this.mouseupSelection)
    window.removeEventListener("scroll", this.handleScroll)
    window.removeEventListener('keydown', this.clickKeydown)
    window.removeEventListener('keyup', this.clickKeyup)
  }
}
</script>
<style lang='less' scoped>
#se {
  position: relative;
  width: 100%;
  height: 100%;
  -moz-user-select: none; /*火狐*/
  -webkit-user-select: none; /*webkit浏览器*/
  -ms-user-select: none; /*IE10*/
  -khtml-user-select: none; /*早期浏览器*/
  user-select: none;
}
ul {
  display: flex;
  flex-wrap: wrap;
  width: 500px;
  margin: 50px auto;
  li {
    box-sizing: border-box;
    padding: 5px 10px;
    border: 1px solid #ccc;
    margin: 10px;
    cursor: pointer;
  }
  .isS {
    background: #99f;
  }
}
#selection {
  position: fixed;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: linear-gradient(
    to top left,
    rgba(218, 27, 198, 0.5),
    rgba(255, 222, 32, 0.2)
  );
  color: rgb(36, 138, 129);
  font-size: 16px;
  width: 0px;
  height: 0px;
  overflow: hidden;
}
</style>

欢迎提出宝贵意见。
喜欢请点赞!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
8490 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
2778 0
vue中使用better-scroll实现滑动效果
一、首先需要在项目中引入better-scroll 1. 在package.json 直接写入 "better-scroll":"^1.11.1"  版本以github上为准(目前最新) 2.cpnm install  在node_modules  可以查看版本是否安装 3.直接在你的组件里面写入import BScroll from 'better-scroll'; 二、better-scroll优点 1.体验像原生:滚动非常流畅,而且没有滚动条。
1651 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
10302 0
基于vue+uniapp直播室实例|仿陌陌/抖音效果
Uni直播是一个运用uni-app+nvue+vuex等技术实现的类似抖音/陌陌功能的直播聊天室项目,功能效果类似陌陌直播上下滑动切换,有聊天/礼物/评论等功能。 预览效果 如上图:可编译到多端,在小程序/H5端/App端效果基本一致 技术栈 编辑器+技术:HBuilderX2.
7754 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
11394 0
winfrom 树形控件如何实现鼠标经过节点时光标颜色改变效果
  一、winform TreeView控件的实现方式。  ///         /// Handles the MouseMove event of the treeView1 control.
934 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6556 0
+关注
Villin
一个前端小白,大家一起学习
30
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载