效果:
前言
此效果依赖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
}
}
结语
可以在此基础上做更多的扩展,根据需求吧。
成语
5/22/2023, 9:49:40 AM
梦珀 作者
8/12/2024, 5:59:27 PM