使用React+TS编写逻辑代码,less编写样式代码,不依赖第三方库,开箱即用,
最近写的拖拽组件,分享给大家,直接上代码
首先看看如何使用
自己定义的组件需要包裹在DragList.Item组件中
import DragList from './DragList';
{
console.log(info)
}}
>
111
222
333
444
555
组件展示
TS 部分
import React, { useState, useEffect, useRef, useMemo, useLayoutEffect } from "react";
import styles from "./index.module.less"
export interface listDataChangeType {
originIndex: number,// 移动前的索引 或 点击删除的索引 或者新增的组件的索引
targetIndex?: number,// 移动后的目标索引
type: 'move' // 操作的类型
}
interface DragListProps {
listDataChange: (changeInfo: listDataChangeType) => void //数据变化
children: React.ReactNode
}
enum MoveDownAndUp {
down = 1,
up = -1
}
const DragItem: React.FC<{children:React.ReactNode}> = ({ children }) => {
useLayoutEffect(() => {
let dragItem = Array.from(document.querySelectorAll('div[data-drag-warper]'))
// 最大值
let maxHeight = Math.max.apply(Math, dragItem.map(item => { return item['clientHeight'] }))
dragItem.forEach((item: any) => {
item.style.setProperty('--drag-offest-height', `${maxHeight}px`)
})
}, [])
return {children}
}
const DragList: React.FC = ({ children, listDataChange}) => {
/** 是否显示遮罩层,实现移动和鼠标松开 */
const [dragging, setDragging] = useState(false);
/** 当前索引数 */
const [draggingItemIndex, setDraggingItemIndex] = useState(-1);
const [originIndex, setOriginIndex] = useState(-1)
/** 当前点击移动列的y轴偏移量 */
const [startPageY, setStartPageY] = useState(0);
/** 设置移动列偏移量 */
const [offsetY, setOffsetY] = useState(0);
// 偏移的高度
const [lineHeight, setLineHeight] = useState(0)
const listContainer = useRef(null)
const [childList, setChildList] = useState([])
const [popoverOpen, setPopoverOpen] = useState(false)
// 处理childran
const validChildren: any = useMemo(() => {
return React.Children.map(children, (child) => {
if (!React.isValidElement(child)) return null
if (child.type !== DragItem) {
console.error('The children of `DragList` must be `DragList.Item` components.')
return null
}
return React.cloneElement(child, { ...child.props })
})
}, [children])
// 计算偏移位置
useEffect(() => {
let clientHeight = listContainer?.current?.clientHeight || 0
let length = childList.length
if (clientHeight > 0) {
setLineHeight((clientHeight - 10 * (length - 1)) / length)
}
}, [childList])
// 避免修改validChildren
useEffect(() => {
setChildList(validChildren)
}, [validChildren])
/** 拖拽开始 */
const onDragStart = (event: React.DragEvent, index: number) => {
if (childList.length === 1) return
setDragging(true);
setDraggingItemIndex(index);
setStartPageY(event.pageY);
setOriginIndex(index)
};
/** 鼠标松开 */
const handleMouseUp = () => {
if (childList.length === 1) return
if (originIndex !== draggingItemIndex) {
// 不相等说明移动了
listDataChange({
originIndex,
targetIndex: draggingItemIndex,
type: 'move'
})
}
// 还原配置
setDragging(false);
setDraggingItemIndex(-1);
setOriginIndex(-1)
setStartPageY(0);
};
/**
* 当拖放生效时,重新整理数组
* @param startIndex 当前拖放列的序号
* @param moveDownAndUp 向下移动 1 向上移动-1
*/
const move = (startIndex: number, moveDownAndUp: MoveDownAndUp) => {
// 获取当前拖动的内容
let newList = [...childList]
let moveItem = newList.splice(startIndex, 1)[0];
newList.splice(startIndex + moveDownAndUp, 0, moveItem);
setChildList(newList)
};
/** 鼠标移动 */
const handleMouseMove = (event: React.MouseEvent) => {
if (childList.length === 1) return
let offset = event.pageY - startPageY;
let draggingIndex = draggingItemIndex;
//当移动的item没有超过list的长度, 则每往下移动超过lineHeight,就把数组中数据往后挪一位。相应的draggingItemIndex 和 startPageY都要增加一位。
if (offset > lineHeight && draggingIndex < childList.length - 1) {
offset -= lineHeight;
move(draggingIndex, MoveDownAndUp.down);
setDraggingItemIndex(draggingIndex + MoveDownAndUp.down);
setStartPageY(startPageY + lineHeight);
//当移动的item还是list里面, 则每往上移动超过lineHeight,就把数组中数据往前挪一位。相应的draggingItemIndex 和 startPageY都要减少一位。
} else if (offset < -lineHeight && draggingIndex > 0) {
offset += lineHeight;
move(draggingIndex, MoveDownAndUp.up);
setDraggingItemIndex(draggingIndex + MoveDownAndUp.up);
setStartPageY(startPageY - lineHeight);
}
setOffsetY(offset);
};
/** 拖动的样式 */
const getDraggingStyle = (index: number): React.CSSProperties => {
if (index === draggingItemIndex) {
return {
transform: `translate(10px, ${offsetY}px)`,
color: '#40a9ff',
opacity: 0.8
};
} else {
return {};
}
};
const ListItem: React.FC = () => {
return <>
{childList.map((child: React.ReactElement, index: number) => (
1}
onDragStart={(event: React.DragEvent) => onDragStart(event, index)}
style={getDraggingStyle(index)}
className={styles['list-item']}>
{child}
))}
>
}
const CoverMask: React.FC = () => {
return dragging ? : <>>
}
return (
);
};
function attachPropertiesToComponent>(
component: C,
properties: P,
): C & P {
const ret = component as any
for (const key in properties) {
if (Reflect.has(properties, key)) {
ret[key] = properties[key]
}
}
return ret
}
export default attachPropertiesToComponent(DragList, {
Item: DragItem
})
CSS 部分
.listContainer {
position: relative;
}
.listContainer .coverMask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(57, 243, 0, 0);
}
.list-item{
background: #fff;
min-height: 50px;
padding: 0 10px;
margin: 10px 0;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
border-radius: 4px;
&-title{
padding: 0 10px;
height: 100px;
flex: 1;
text-align: left;
}
&-delete{
&:hover{
fill: unset;
}
}
&-move{
cursor: pointer;
&:hover{
fill: unset;
}
}
&:hover{
&-delete{
fill: unset;
}
color: #40a9ff;
}
}
.header{
display: flex;
justify-content: flex-end;
&-add{
background: #40a9ff;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
transition: all;
border-radius: 50%;
.svg{
fill: #fff;
}
}
&-close{
background: #fff;
transition: all;
transform: rotate(45deg);
.svg{
fill: #40a9ff;
}
}
}
.drag-item{
height: var(--drag-offest-height);
overflow: auto;
max-height: 500px;
width: 100%;
}