简单写一个拖拽效果

855 阅读2分钟

本文是基于拖拽事件 drag 事件| MDN (mozilla.org)完成的

简介Drag

当一个元素的draggable属性设置为true的时候,既表示元素是可以拖动的(图片和链接的draggable属性默认为true) 所以, 我们要实现拖拽功能必须给一个标签属性draggable值设置为true

  • dragstart 事件在用户开始拖动元素或被选择的文本时调用
  • dragend 事件在拖放操作结束时触发(通过释放鼠标按钮或单击 escape 键)
  • dragover 事件在可拖动的元素或者被选择的文本被拖进一个有效的放置目标时(每几百毫秒)触发,会一直触发。这里的有效放置目标需要怎么理解呢? 假如我们有一块区域,我们要把可拖拽的元素放到这里面进去, 那么我们就要给这块区域的元素添加 dragover 事件,当文本被拖拽到此区域的时候就会一直触发该事件。
  • dragenter 事件在可拖动的元素或者被选择的文本进入一个有效的放置目标时触发。与dragover不同的是, dragenter 事件在有效放置目标区域内不会一直触发, 只有在进入的时候触发一次,而dragover每几百毫秒就会触发一次

案例分析

  1. 我们先准备一个swapper容器
  2. 创建列表ul, li 把他们的draggable属性设置为true并添加数据,这样每个li 都是可拖拽的了
 function create() {
     const dargList = document.createElement('ul')
     dargList.classList.add('dargList')
     data.forEach(itemLabel => {
         const item = document.createElement('li')
         item.draggable = true
         item.classList.add('drag-item')
         item.innerText = itemLabel
         dargList.appendChild(item)
     })
     swapper.appendChild(dargList)
     return dargList
 }
  1. 为每个li添加dragstart事件和dragend事件, 当拖拽的时候, 为该元素添加dragging类,取消拖拽的时候移除dragging
        function bindEven() {
            // 每个 item 添加事件
            const items = document.querySelectorAll('.drag-item')
            // 转为真正得数组
            const drag_items = [...items]
            drag_items.forEach(drag_item => {
                drag_item.addEventListener('dragstart', handleDragStar)
                drag_item.addEventListener('dragend', handleDragEnd)
            })
        }
        
        function handleDragStar(e) {
            // 为什么要添加定时器? 
            // 如果不添加定时器, 按下去的瞬间,透明度为0 , 那么拖拽层透明度也为0 
            // 定时器是异步任务, 添加了定时器按下去瞬间透明度还不是0 , 那么拖拽层透明度就不为0
            setTimeout(() => {
                this.classList.add('dragging')
            }, 0)
        }
        
        // 拖拽结束的时候移除类名
        function handleDragEnd() {
            this.classList.remove('dragging')
        }
  1. 为ul容器添加dragover事件和dragenter事件
            const dargList = document.querySelector('.dargList')
            dargList.addEventListener('dragover', handleDragOver)
            dargList.addEventListener('dragenter', handlePreventDefault)
        // 放置到某个地方的时候触发: 被拖进一个有效的放置目标时(每几百毫秒)触发, 在拖拽的时候会一直触发
        function handleDragOver(e) {
            e.preventDefault();
            // this 表示整个ul
            const dragging = this.querySelector('.dragging')
            // notDragging表示 除了被选中以外的其他所有 li
            const notDragging = this.querySelectorAll('.drag-item:not(.dragging)')
            // 比如要插入3-4中间, 就得满足鼠标位置比3大, 所以就得找到第一个比3大的元素, 就通过find去找
            const insertwhitchDom = [...notDragging].find(item =>
            (e.clientY <= (item.offsetTop + item.offsetHeight / 2)
            ))
            this.removeChild(dragging)
            this.insertBefore(dragging, insertwhitchDom)
        }
        
        // 取消默认行为
        function handlePreventDefault(e) {
            e.preventDefault();
        }

完整代码

<body>
<div class="drag-swapper"></div>

<script>
    const swapper = document.querySelector('.drag-swapper')
    const data = [
        '111111111111111',
        '222222222222222',
        '333333333333333',
        '444444444444444',
        '555555555555555'
    ]
    function create() {
            const dargList = document.createElement('ul')
            dargList.classList.add('dargList')
            data.forEach(itemLabel => {
                const item = document.createElement('li')
                item.draggable = true
                item.classList.add('drag-item')
                item.innerText = itemLabel
                dargList.appendChild(item)
            })
            swapper.appendChild(dargList)
            return dargList
        }
    function render() {
            // ul列表
            const dargList = create()
        }
    function bindEven() {
        // 每个 item 添加事件
        const items = document.querySelectorAll('.drag-item')
        // 转为真正得数组
        const drag_items = [...items]
        drag_items.forEach(drag_item => {
            drag_item.addEventListener('dragstart', handleDragStar)
            drag_item.addEventListener('dragend', handleDragEnd)
        })
        const dargList = document.querySelector('.dargList')
        dargList.addEventListener('dragover', handleDragOver)
        dargList.addEventListener('dragenter', handlePreventDefault)
        window.addEventListener('dragenter', handlePreventDefault)
    }
    function handleDragStar(e) {
            // 为什么要添加定时器? 
            // 如果不添加定时器, 按下去的瞬间,透明度为0 , 那么拖拽层透明度也为0 
            // 定时器是异步任务, 添加了定时器按下去瞬间透明度还不是0 , 那么拖拽层透明度就不为0
            setTimeout(() => {
                this.classList.add('dragging')
            }, 0)
        }
    // 放置到某个地方的时候触发: 被拖进一个有效的放置目标时(每几百毫秒)触发, 在拖拽的时候会一直触发
    function handleDragOver(e) {
        e.preventDefault();
        // this 表示整个ul
        const dragging = this.querySelector('.dragging')
        // notDragging表示 除了被选中以外的其他所有 li
        const notDragging = this.querySelectorAll('.drag-item:not(.dragging)')
        // 比如要插入3-4中间, 就得满足鼠标位置比3大, 所以就得找到第一个比3大的元素, 就通过find去找
        const insertwhitchDom = [...notDragging].find(item =>
        (e.clientY <= (item.offsetTop + item.offsetHeight / 2)
        ))
        this.removeChild(dragging)
        this.insertBefore(dragging, insertwhitchDom)
    }
    // 拖拽结束的时候移除类名
    function handleDragEnd() {
        this.classList.remove('dragging')
    }
    function handlePreventDefault(e) {
        e.preventDefault();
    }
    function init() {
        render()
        bindEven()
    }
    init()
</script>
</body>
<style>
    * {
        margin: 0;
        padding: 0;
    }
    li {
        list-style: none;
    }
    .drag-swapper {
        padding: 20px;
        width: 500px;
        margin: 100px auto;
        box-shadow: 1px 3px 5px #ccc;
    }
    .drag-swapper .drag-item {
        margin-top: 20px;
        height: 30px;
        line-height: 30px;
        padding-left: 20px;
        color: #000;
        background-color: pink;
        border: 1px solid #999;
    }
    .drag-swapper .drag-item.dragging {
        opacity: 0;
    }
</style>

效果

可前往拖拽 - 码上掘金 (juejin.cn)查看最终效果

image.png

image.png

image.png