实现一个可拖拽的vue指令

代码

drag.js

import Vue from 'vue'
Vue.directive('dragx',(el, binding, vnode) => {
    //  默认参数
    let defaultOpts = {
        dragDirection: 'n, e, s, w, ne, se, sw, nw, all', // 东 e 西 w 南 s 北 n 东北ne 东南 se 西南 sw 西北 nw 全部 all
        dragContainerId: '',
        dragBarClass: '',  // 类选择器
        resizeEdge: 10,
        dirctDom: true,
        canDrag: true,
        canResize: true
    }
    let isMove = false;
    binding.value = binding.value || {};
    let cfg = Object.assign({}, defaultOpts, binding.value);

    // 获取目标元素 resize方向
    function getDirection(e) {
        let el = e.currentTarget;
        let dir = '';
        let rect = el.getBoundingClientRect();
        let win = el.ownerDocument.defaultView;
        let offset = {
            top: rect.top + win.pageYOffset,
            left: rect.left + win.pageXOffset,
            right: rect.right + win.pageXOffset,
            bottom: rect.bottom + win.pageYOffset,
        }
        // 东 E    西 W   南 S   北 N
        if(e.pageY > offset.top && e.pageY < offset.top + cfg.resizeEdge){
            dir += 'n'
        }
        else if(e.pageY < offset.bottom && e.pageY > offset.bottom - cfg.resizeEdge){
            dir += 's'
        }
        if (e.pageX > offset.left && e.pageX < offset.left + cfg.resizeEdge) {
            dir += 'w';
        }
        else if (e.pageX < offset.right && e.pageX > offset.right - cfg.resizeEdge) {
            dir += 'e';
        }
        if(binding.value) {
            let directions = cfg.dragDirection.split(',');
            for (let i = 0; i < directions.length; i++) {
                let handle = directions[i].replace(/(^\s*)|(\s*$)/g, '');
                if(handle == 'all' || handle === dir){
                    return dir
                }
            }
        }
        return '';
    }

    el.onmousedown = function (e) {
        isMove = false;
        // drag1
        if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)) {
            isMove = true;
            document.body.style.cursor = 'move';
        }
        let dir = getDirection(e);
        let style = window.getComputedStyle(el);
        function getStyleNumValue(key) {
            return parseInt(style.getPropertyValue(key), 10)
        }
        let rect = el.getBoundingClientRect();
        let data = {
            width: getStyleNumValue("width"),
            height: getStyleNumValue("height"),
            left: getStyleNumValue("left"),
            top: getStyleNumValue("top"),
            borderLeft:getStyleNumValue("border-left-width"),
            borderTop:getStyleNumValue("border-top-width"),
            borderRight:getStyleNumValue("border-right-width"),
            borderBottom:getStyleNumValue("border-bottom-width"),
            deltX: e.pageX - rect.left, // 鼠标在元素内位置
            deltY: e.pageY - rect.top,
            startX: rect.left,  // 元素的位置
            startY: rect.top
        };
        if (dir === '' && !isMove) return;
        // 创建遮罩
        let mask = document.createElement("div");
        mask.style.cssText = "position:absolute;top:0px;bottom:0px;left:0px;right:0px;";
        document.body.appendChild(mask);
        document.onmousemove = function (edom) {
            // 获取当前鼠标位置
            // 右
            if (dir.indexOf("e") > -1) {
                data.width = edom.pageX - data.startX + data.borderLeft+data.borderRight;
            }
            // 下
            if (dir.indexOf("s") > -1) {
                data.height = edom.pageY - data.startY + data.borderBottom+data.borderTop;
            }
            // 上
            if (dir.indexOf("n") > -1) {
                let deltheight = data.startY + data.borderBottom + data.borderTop - edom.pageY;
                data.height += deltheight;
                data.top -= deltheight;
                data.startY -= deltheight;
            }
            // 左
            if (dir.indexOf("w") > -1) {
                let deltwidth = data.startX + data.borderLeft+data.borderRight- edom.pageX;
                data.width += deltwidth;
                data.left -= deltwidth;
                data.startX -= deltwidth;
            }
            // 移动
            if(isMove && cfg.canDrag){
                let targetPageX = edom.pageX;
                let targetPageY = edom.pageY;
                let deltX = targetPageX - data.startX - data.deltX;
                let deltY = targetPageY - data.startY - data.deltY;
                let newLeft = parseInt(getStyleNumValue('left') || '0', 10) + deltX;
                let newTop = parseInt(getStyleNumValue('top') || '0', 10) + deltY;
                console.log(window.innerWidth + data.borderLeft+data.borderRight - data.width - cfg.resizeEdge)
                if(newLeft <= 0){
                    newLeft = 0
                }
                else if(newLeft >= window.innerWidth - data.width - cfg.resizeEdge){
                    newLeft = window.innerWidth - data.width - cfg.resizeEdge
                }
                if(newTop <= 0){
                    newTop = 0
                }
                else if(newTop >= window.innerHeight - data.height - cfg.resizeEdge){
                    newLeft = window.innerHeight - data.height - cfg.resizeEdge
                }
                data.left = newLeft;
                data.top = newTop;
                data.startX = data.startX + deltX;
                data.startY = data.startY + deltY;
            }
            // 缩放
            if (cfg.dirctDom) {
                if(cfg.canResize){
                    el.style.width = data.width + "px";
                    el.style.height = data.height + "px";
                }
                if(cfg.canDrag){
                    el.style.left = data.left + 'px';
                    el.style.top = data.top + 'px';
                }
            }
            el.dispatchEvent(new CustomEvent('bindUpdate', {detail: data}))
        }
        document.onmouseup = function (e) {
            document.body.style.cursor = '';
            document.onmousemove = null;
            document.onmouseup = null;
            isMove = false;
            document.body.removeChild(mask);
        }
        document.body.style.cursor = dir + '-resize';
    }

    el.onmousemove = function (e) {
        if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)&&cfg.canDrag) {
            el.style.cursor = 'move';
            return;
        }
        let dir = getDirection(e);
        if (dir !== '') {
            el.style.cursor = dir + '-resize';
            return;
        }
        el.style.cursor = '';
    }

    el.onmouseleave = function (e) {
        el.style.cursor = '';
    }
})

vue

<template>
        <div id="app1">
            <div id="box1" v-dragx="dragBox1">
                <div class="drag1"><b>开始拖拽</b></div>
            </div>
        </div>
</template>

<script>
    import "./../directive/dragx";
    export default {
        name: "App",
        data() {
            return {
                dragBox1: {
                    dragBarClass: "drag1",
                    dragContainerId: "app1"
                }
            };
        },
        methods:{
            bindUpdate(event){
                let data=event.detail;
              	console.log(data)
            }
        }
    };
</script>

<style>
    #app {
        font-family: "Avenir", Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        border: dashed 1px #000;
        height: 500px;
        position: relative;
    }
    #box1 {
        width: 100px;
        height: 100px;
        border: solid 1px rgba(33, 61, 223, 0.541);
        position: absolute;
        top: 50px;
        left: 200px;
        background-color: #2d7bf4;
    }
    .drag1{
        background-color:#ccc;
        border-top: solid 1px rgba(33, 61, 223, 0.541);
        border-bottom: solid 1px rgba(33, 61, 223, 0.541);
    }
</style>

开发思路

  • 通过鼠标移动实现组件移动,改变大小,一定需要操作dom,查看vue官方文档,从实用性,已经通用性,选择开发一个自定义vue指令

  • 通过鼠标移动产生的位移,动态改变大小或位置

  • 通过事件通知方式,实现更新bind值

所以有了以上思路,就需要一次掌握三个重要知识

  1. vue 如果开发一个自定义指令

  2. 鼠标移动过程中,MouseEvent对象各种值的含义

  3. 如何使用并分发一个自定义事件

vue指令文档https://cn.vuejs.org/v2/guide/custom-directive.html#ad

vue对开发着提供(bind、inserted、update、componentUpdated、unbind)注入点。

  • bind 只调用一次,指令第一次绑定到元素时调用,主要完成指令初始化设置

  • inserted 被绑定元素插入父节点时调用,官网对这里有一个强调 仅保证父节点存在,但不一定被插入文档中

  • update 所在组件的VNode更新时调用,但也可能发生在其子VNode更新之前,所以提供的参数中包含 vnode oldVnode,具体是否需要响应操作,可以比较这两个node中对应的值是否变化

  • componentUpdated 指令所在组件的 VNode极其子VNode全部更新后调用,此钩子函数的补充,就可以解决update钩子,不及时更新问题。

  • unbind 只调用一次,指令与元素解绑是调用

每一个钩子都带有参数 el、binding、vnode、oldVnode

实现

  • 获取鼠标在目标元素上方向
//  默认参数
    let defaultOpts = {
        dragDirection: 'n, e, s, w, ne, se, sw, nw, all', // 东 e 西 w 南 s 北 n 东北ne 东南 se 西南 sw 西北 nw 全部 all
        dragContainerId: '',
        dragBarClass: '',  // 类选择器
        resizeEdge: 10,
        dirctDom: true,
        canDrag: true,
        canResize: true
    }
    let isMove = false;
    binding.value = binding.value || {};
    let cfg = Object.assign({}, defaultOpts, binding.value);

    // 获取目标元素 resize方向
    function getDirection(e) {
        let el = e.currentTarget;
        let dir = '';
        let rect = el.getBoundingClientRect();
        let win = el.ownerDocument.defaultView;
        let offset = {
            top: rect.top + win.pageYOffset,
            left: rect.left + win.pageXOffset,
            right: rect.right + win.pageXOffset,
            bottom: rect.bottom + win.pageYOffset,
        }
        // 东 E    西 W   南 S   北 N
        if(e.pageY > offset.top && e.pageY < offset.top + cfg.resizeEdge){
            dir += 'n'
        }
        else if(e.pageY < offset.bottom && e.pageY > offset.bottom - cfg.resizeEdge){
            dir += 's'
        }
        if (e.pageX > offset.left && e.pageX < offset.left + cfg.resizeEdge) {
            dir += 'w';
        }
        else if (e.pageX < offset.right && e.pageX > offset.right - cfg.resizeEdge) {
            dir += 'e';
        }
        if(binding.value) {
            let directions = cfg.dragDirection.split(',');
            for (let i = 0; i < directions.length; i++) {
                let handle = directions[i].replace(/(^\s*)|(\s*$)/g, '');
                if(handle == 'all' || handle === dir){
                    return dir
                }
            }
        }
        return '';
    }
  • 判断是否在目标元素里
cfg.dragBarClass.length > 0 
&& e.target.classList.contains(cfg.dragBarClass)
&& cfg.canDrag
  • 鼠标点击目标元素
el.onmousedown = function (e) {
        isMove = false;
        // drag1
        if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)) {
            isMove = true;
            document.body.style.cursor = 'move';
        }
        let dir = getDirection(e);
        let style = window.getComputedStyle(el);
        function getStyleNumValue(key) {
            return parseInt(style.getPropertyValue(key), 10)
        }
        let rect = el.getBoundingClientRect();
        let data = {
            width: getStyleNumValue("width"),
            height: getStyleNumValue("height"),
            left: getStyleNumValue("left"),
            top: getStyleNumValue("top"),
            borderLeft:getStyleNumValue("border-left-width"),
            borderTop:getStyleNumValue("border-top-width"),
            borderRight:getStyleNumValue("border-right-width"),
            borderBottom:getStyleNumValue("border-bottom-width"),
            deltX: e.pageX - rect.left, // 鼠标在元素内位置
            deltY: e.pageY - rect.top,
            startX: rect.left,  // 元素的位置
            startY: rect.top
        };
        if (dir === '' && !isMove) return;
        // 创建遮罩
        let mask = document.createElement("div");
        mask.style.cssText = "position:absolute;top:0px;bottom:0px;left:0px;right:0px;";
        document.body.appendChild(mask);
        document.body.style.cursor = dir + '-resize';
}
  • 鼠标在目标元素上移动
document.onmousemove = function (edom) {
            // 获取当前鼠标位置
            // 右
            if (dir.indexOf("e") > -1) {
                data.width = edom.pageX - data.startX + data.borderLeft+data.borderRight;
            }
            // 下
            if (dir.indexOf("s") > -1) {
                data.height = edom.pageY - data.startY + data.borderBottom+data.borderTop;
            }
            // 上
            if (dir.indexOf("n") > -1) {
                let deltheight = data.startY + data.borderBottom + data.borderTop - edom.pageY;
                data.height += deltheight;
                data.top -= deltheight;
                data.startY -= deltheight;
            }
            // 左
            if (dir.indexOf("w") > -1) {
                let deltwidth = data.startX + data.borderLeft+data.borderRight- edom.pageX;
                data.width += deltwidth;
                data.left -= deltwidth;
                data.startX -= deltwidth;
            }
            // 移动
            if(isMove && cfg.canDrag){
                let targetPageX = edom.pageX;
                let targetPageY = edom.pageY;
                let deltX = targetPageX - data.startX - data.deltX;
                let deltY = targetPageY - data.startY - data.deltY;
                let newLeft = parseInt(getStyleNumValue('left') || '0', 10) + deltX;
                let newTop = parseInt(getStyleNumValue('top') || '0', 10) + deltY;
                console.log(window.innerWidth + data.borderLeft+data.borderRight - data.width - cfg.resizeEdge)
                if(newLeft <= 0){
                    newLeft = 0
                }
                else if(newLeft >= window.innerWidth - data.width - cfg.resizeEdge){
                    newLeft = window.innerWidth - data.width - cfg.resizeEdge
                }
                if(newTop <= 0){
                    newTop = 0
                }
                else if(newTop >= window.innerHeight - data.height - cfg.resizeEdge){
                    newLeft = window.innerHeight - data.height - cfg.resizeEdge
                }
                data.left = newLeft;
                data.top = newTop;
                data.startX = data.startX + deltX;
                data.startY = data.startY + deltY;
            }
            // 缩放
            if (cfg.dirctDom) {
                if(cfg.canResize){
                    el.style.width = data.width + "px";
                    el.style.height = data.height + "px";
                }
                if(cfg.canDrag){
                    el.style.left = data.left + 'px';
                    el.style.top = data.top + 'px';
                }
            }
            el.dispatchEvent(new CustomEvent('bindUpdate', {detail: data}))
        }
        document.onmouseup = function (e) {
            document.body.style.cursor = '';
            document.onmousemove = null;
            document.onmouseup = null;
            isMove = false;
            document.body.removeChild(mask);
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值