使用JS实现拖拽排序

使用js实现鼠标拖拽排序效果,使用dra

阅读量:1026

点赞量:47

评论量:2
Skill
标签摘要
使用js实现鼠标拖拽排序效果,使用dragEvent实现
正文


效果:

GIF1.gif

前言

此效果依赖Flip,详情请看这篇文章:Flip效果、原生js实现过渡动画

关于DragEvent:DragEvent

实现思路

1、初始化拖拽容器内的子元素,使其可以拖拽。

Array.from(children).forEach(item => item.setAttribute('draggable', true))

2、拖拽时记录拖拽的元素

box.addEventListener('dragstart', (e: DragEvent) => {
  this.dragElement = e.target
})

3、拖拽过程中,判断放置的元素是否为拖拽容器下的直属子元素,且不为拖拽元素。

box.addEventListener('dragover', (e: DragEvent) => {
  const dropElement = e.target
  const isChildren = Array.from(box.children).some(
    (item) => item === dropElement
  )
  const isOwn = dropElement === this.dragElement
  if (!isOwn && isChildren && this.animateEnd) {
    // 准备换位置
  }
})

4、将拖拽元素移动到放置元素的前面或后面。(此处使用的是:如果拖拽元素在放置元素前面,将拖拽元素放到放置元素后面。否则,将拖拽元素放到放置元素前面。也可以通过位置判断,效果会好,不过没有这样简单。)

const isAtDropBefore = Array.from(box.children).find(
  (item) => item === this.dragElement || item === this.dropElement
) === this.dragElement
if (isAtDropBefore) {
  box.insertBefore(this.dragElement, this.dropElement.nextElementSibling)
} else {
  box.insertBefore(this.dragElement, this.dropElement)
}

5、使用Flip实现过渡效果。

const f = new MpFlip(box.children)
// ...换位置的代码
f.play(this.options.animateTime)
this.animateEnd = false
setTimeout(() => {
  this.animateEnd = true
}, this.options.animateTime)

6、拖拽结束后,调用回调方法,方便后面使用。

el.addEventListener('dragend', () => {
  if (typeof this.options.callback === 'function') {
    this.options.callback.call(
      null,
      this.dragElement,
      this.dropElement,
      this.isAtDropBefore()
    )
  }
})

完整代码

index.html

<div id="app">
  <div><span>1</span></div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
  <div>10</div>
</div>
<script type="module" src="/src/drag.ts"></script>
<script>
  new MpDrag({
    el: '#app',
    animateTime: 150,
    callback: (drag, drop, isBefore) => {
      console.log({ drag, drop, isBefore })
    }
  })
</script>

drag.ts

import { MpFlip } from './flip'

export interface IOptionsType {
  el: string | HTMLElement
  animateTime: number
  callback: (
    dragElement: Element,
    dropElement: Element,
    isAtDropBefore: boolean
  ) => void
}

export class MpDrag {
  private element: HTMLElement
  private options: Partial<IOptionsType> = {
    animateTime: 150
  }
  private dragElement: HTMLElement
  private dropElement: HTMLElement
  private animateEnd: boolean = true

  constructor(options: Partial<IOptionsType>) {
    Object.assign(this.options, options)
    this.init()
  }

  /**
   * 初始化
   */
  init() {
    if (typeof this.options.el === 'string') {
      this.element = document.querySelector(this.options.el) as HTMLElement
    } else if (this.options.el instanceof Element) {
      this.element = this.options.el
    } else {
      throw '元素未找到'
    }
    this.initChildren()
    this.addEvent()
  }

  /**
   * 初始化拖拽容器下的子元素
   * @returns
   */
  initChildren() {
    const children = this.element.children
    for (let i = 0; i < children.length; i++) {
      children[i].setAttribute('draggable', 'true')
    }
  }

  /**
   * 添加拖拽事件监听
   */
  addEvent() {
    const el = this.element
    // 记录拖拽元素
    el.addEventListener('dragstart', (e: DragEvent) => {
      this.dragElement = e.target as HTMLElement
    })
    // 判断放置元素,此处可以监听dragover或者dragenter,效果略有不同
    el.addEventListener('dragover', (e: DragEvent) => {
      const dropElement = e.target as HTMLElement
      const isChildren = Array.from(el.children).some(
        (item) => item === dropElement
      )
      const isOwn = dropElement === this.dragElement
      if (!isOwn && isChildren && this.animateEnd) {
        this.dropElement = dropElement
        const f = new MpFlip(el.children)
        // 如果拖拽元素在放置元素前面,将拖拽元素放到放置元素后面
        if (this.isAtDropBefore()) {
          el.insertBefore(this.dragElement, this.dropElement.nextElementSibling)
        // 否则,将拖拽元素放到放置元素前面
        } else {
          el.insertBefore(this.dragElement, this.dropElement)
        }
        f.play(this.options.animateTime)
        this.animateEnd = false
        setTimeout(() => {
          this.animateEnd = true
        }, this.options.animateTime)
      }
    })

    // 拖拽结束
    el.addEventListener('dragend', () => {
      // 此处可以做一些回调, 加一下回调参数
      if (typeof this.options.callback === 'function') {
        this.options.callback.call(
          null,
          this.dragElement,
          this.dropElement,
          this.isAtDropBefore()
        )
      }
    })
  }

  /**
   * 拖拽元素是否在放置元素之前
   */
  isAtDropBefore() {
    const el = this.element
    const beforeElement = Array.from(el.children).find(
      (item) => item === this.dragElement || item === this.dropElement
    )
    return beforeElement === this.dragElement
  }
}

结语

可以在此基础上做更多的扩展,根据需求吧。

发布于: 2/2/2023, 12:44:55 PM
最后更新: 12/3/2024, 3:30:17 AM
给我留言
访客留言
  • 成语

    5/22/2023, 9:49:40 AM

    回复
    大哥缺个flip文件把
    • 梦珀 作者

      8/12/2024, 5:59:27 PM

      回复
      上面有链接//@成语:大哥缺个flip文件把