欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue组件开发之-可拖拽对话框

程序员文章站 2024-01-31 13:37:28
...

前言:最近工作大忙,项目功能新增频繁,客户得美观追求高,带来了工作量的激增,心里不禁想长叹一句:这种审美能力如果能在项目之初展现就好了。无心之际,想起了之前写的对话框的可拖拽效果,今天拿来再完善一下。

正文:

1.初衷:加拖的初衷是饿了么ui对话框没有拖拽效果,感觉体验不好,开始的做法是在组件的钩子里添加事件监听,如果想在其他地方使用,必须再写同样的监听逻辑,虽然我们可以把代码抽离出来,但多处引用的方式依然,本质问题仍在,复用率低代码冗余高,果断抛弃。

2.查资料:翻看vue文档发现可以注册全局指令,用于DOM元素的底层处理,这个挺靠谱。

Vue.directive( 指令名,{ 钩子函数 } )
vue组件开发之-可拖拽对话框

3.思路:很简单:首先记录整个对话框距离文档边界的左边距和上边距(当然你监听右边距也oK的,但是别监听下边距,因为我们一般是设置上边距,饿了么也是如此。正如我们调整高度时,会说往下拉一点,或者往上去一点)。鼠标按下的时候监听mousedown事件,记录鼠标距离对话框边界的距离(左边距LEFT,上边距TOP).然后监听document的mousemove事件,因为我们不可能确保我们的鼠标一直都在框中拖拽,因为我们速度有慢有快,在mousemove中保证鼠标与对话框的位置跟mousedown时一致。鼠标mouseup时移除mousemove和mouseup的监听,下次对话框打开时再把它的位置还原如初

4.思考过程:

  1. 使用哪些钩子?  因为我们要记录初始位置,并保存记录,采用inserted钩子,可以保证我们能正确地访问到相关样式属性,在componentUpdated里当对话框打开时再把位置还原到拖拽之前的位置。
  2. computedStyle or element.style?因为有的样式写在行内有的样式写在样式表里,思考后决定统一使用getComputedstyle,然后发现有些是空字符"",有些是"auto"我嘞个去。这怎么办?问题到这里已经很清晰了,那就是根据样式来获取距离不靠谱。
  3. 计算获取marginLeft,marginTop,我们通过html的宽度与对话框的宽度之差获取对话框左边距
  4. 再通过鼠标事件里的event.clientX、event.clientY 获取鼠标到对话框的距离。并处理。
  5. componentUpdated里还原位置。
let marginLeft = (compotedStyle(document.firstElementChild).width -computedStyle(dialogEle).width)/2
//此处注意把值转换为数字格式

5.贴上代码:

import Vue from 'vue'
let LEFT;
let TOP;
//指令的传参形式为<dialogo v-dialogdrag=" { target:'selector',container:'.box',dialogVisible:"传入对话框的visible变量" } "></dialog>
Vue.directive('dialogdrag',{
  inserted(el,binding){
    let container = el.querySelector(binding.value.container);
    let target = el.querySelector(binding.value.target);


    let temContainerWidth = getComputedStyle(container).width;
    let temHtmlWidth = getComputedStyle(document.firstElementChild).width;
    if(temContainerWidth.indexOf('%') != -1){
      //百分值
      LEFT = (
        parseFloat(temHtmlWidth) -
        parseFloat(temHtmlWidth) * temContainerWidth.substring(0,temContainerWidth.length-1)/100
      )/2;
    }else if(temContainerWidth.indexOf('px') != -1){
      //像素值
      LEFT = (
        parseFloat(temHtmlWidth) -
        parseFloat(temContainerWidth)
      )/2;
    }else{
      //其他值
      throw ('对话框容器宽度只能为像素或百分比!')
    }
    console.log(temContainerWidth);
    console.log(temHtmlWidth);
    //
    let temMarginTop = getComputedStyle(container).marginTop;
    if(temMarginTop && temMarginTop.indexOf('px') != -1){
      //不为空并且以像素为单位
      TOP = parseFloat(temMarginTop);
    }else{
      throw ('请设置对话框容器上边距margin-top并以像素为单位!')
    }
    console.log(LEFT)
    //删除对话容器的行内样式(marginleft,margintop,marginbottom,marginrigth);
    delete container.style.marginTop;
    delete container.style.marginLeft;
    delete container.style.marginRight;
    delete container.style.marginBottom;
    delete container.style.margin;
    //赋值给marginTop;marginLeft;
    container.style.marginTop = TOP+'px';
    container.style.marginLeft = LEFT+'px';

    //事件监听
    target.addEventListener('mousedown',function(event){
      //获取鼠标距离对话框容器的左上边距
      let leftValue = event.clientX - parseFloat(getComputedStyle(container).marginLeft);
      let topValue = event.clientY - parseFloat(getComputedStyle(container).marginTop);
      document.addEventListener('mousemove',moveFn,true)
      document.addEventListener('mouseup',upFn,true)
      function moveFn(event){
        console.log('还在移动')
        target.style.cursor = 'move';
        container.style.marginLeft = (event.clientX-leftValue)+'px';
        container.style.marginTop = (event.clientY-topValue)+'px';

      }
      function upFn(event){
        target.style.cursor = 'default';
        document.removeEventListener('mousemove',moveFn,true);
        //document.removeEventListener('mouseup',upFn);
      }
    })
  },
  componentUpdated(el,binding){
    if(binding.value.dialogVisible){
      //打开时还原对话框位置
      el.querySelector(binding.value.container).style.marginTop = TOP+'px';

      el.querySelector(binding.value.container).style.marginLeft = LEFT+'px';

    }
  }
})