为了账号安全,请及时绑定邮箱和手机立即绑定

如何用React开发拖拽组件

标签:
React.JS React

拖拽组件是在前端开发中十分常见的一个功能,现在无论你是使用React还是Vue,都有很多现成的拖拽组件可以使用。不过,有些时候你可能还是需要自己去实现,那么就必须需要理解其实现原理。接下来这篇文章,我将详细介绍如何使用React框架来实现一个拖拽组件。

理解HTML5 拖放API

现如今,大部分的前端拖拽组件都依托于HTML5原生提供的拖放接口。那么在开始用具体框架来封装组件的之前,就需要搞清楚这些原生的接口功能。

HTML 5的DOM鼠标事件中添加了drag这个事件,对于一个设置了draggable的页面元素来说,只要将其拖动到一个droppable的元素上,就算完成了一次完整的拖放功能。在这一过程中,会分别触发一些如下事件类型:

事件类型 事件处理函数 含义
drag ondrag 拖放进行中
dragend/dragstart ondragend/ondragstart 开始拖放和结束拖放
dragover ondragover 当元素或选中的文本被拖到一个目标目标上(每100毫秒触发一次)。
dragenter/dragleave ondragenter/ondragleave 源对象开始进入/离开目标对象范围内
drop ondrop 源对象被拖放到目标对象上

熟悉这些基本事件类型后,实现上就是在源对象和目标对象上分别绑定对应的事件处理函数,并监听处理即可。

除了这些拖放的事件接口外,我们通常还需要处理数据的传递。HTML5中同样提供了简便的接口,在对应的监听函数内,我们可以拿到event对象,在这个对象内部有个DataTransfer接口,可专门用来保存事件的数据内容。对应的接口有:

  • event.dataTransfer.setData: 添加拖拽数据,这个方法接收两个参数,第一个参数是数据类型(可自定义),第二个参数是对应的数据
  • event.dataTransfer.getData:反向操作,获取数据,只接收一个参数,即数据类型
  • event.dataTransfer.clearData: 清除数据
  • event.dataTransfer.setDragImage: 可自定义拖放过程中鼠标旁边的图像
  • event.dataTransfer.effectAllowed: 指定拖放操作所允许的一个效果,有多个属性值,如link, move等,具体可参考https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer/effectAllowed

了解完这些基本接口后,我们就可以着手使用React来编写自己的拖放组件了:

实现Drag组件

我们第一个要实现是Drag组件,它会作为我们的源对象,它的子组件都可以进行拖动。就像这样:

<Drag dataItem="item">
	<div>这个组件可以拖动</div>
</Drag>

我们先来实现最基础的功能,通过setData接口来传递数据:

const Drag = (props) => {
    
    const startDrag = ev => {
        // 传输数据
        ev.dataTransfer.setData("drag-item", props.dataItem);
    };
    
    return (
      <div draggable onDragStart={startDrag}>
        {props.children}
      </div>);
}

实现Drop组件

接着我们就要来实现目标组件了,需要定义一个对外暴露的接口用来接收拖拽完成后的事件:

<DropTarget onItemDropped={itemDropped}>
	<div>
		请将组件拖放到这里
    </div>
</DropTarget>

从实现上来说,监听onDragOveronDrop这两个事件就可以了:

const DropTarget = (props) => {
    const dragOver = ev => {
        ev.preventDefault();
    }

	const drop = ev => {
        // 获取数据
        const droppedItem = ev.dataTransfer.getData("drag-item");
        if (droppedItem) {
            // 触发回调函数
            props.onItemDropped(droppedItem);
        }
    }
    
    return (
    	<div onDragOver={dragOver} onDrop={drop}>
        	{props.children}
        </div>
    )
}

添加拖放效果

要实现拖放的视觉效果,需要effectAllowed和dropEffect两个属性结合起来使用。

先在Drag组件上设置effectAllowed属性:

const Drag = (props) => {
    
    const startDrag = ev => {
        ev.dataTransfer.setData("drag-item", props.dataItem);
        // 添加效果
        ev.dataTransfer.effectAllowed = props.dropEffect;
    };
    
    return (
      <div draggable onDragStart={startDrag}>
        {props.children}
      </div>);
}

接着我们设置一些效果常量:

export const All = "all";
export const Move = "move";
export const Copy = "copy";
export const Link = "link";
export const CopyOrMove = "copyMove";
export const CopyOrLink = "copyLink";
export const LinkOrMove = "linkMove";
export const None = "none";

然后在目标组件上,我们通过给dropEffect属性赋值来引用这些效果常量,修改代码如下:

const DropTarget = (props) => {
    const dragOver = ev => {
        ev.preventDefault();
        // 添加效果
        ev.dataTransfer.dropEffect = props.dropEffect;
    }
    
    const dragEnter = ev => {
        ev.dataTransfer.dropEffect = props.dropEffect;
    }

	const drop = ev => {
        const droppedItem = ev.dataTransfer.getData("drag-item");
        if (droppedItem) {
            props.onItemDropped(droppedItem);
        }
    }
    
    return (
    	<div onDragOver={dragOver} onDrop={drop} onDragEnter={dragEnter}>
        	{props.children}
        </div>
    )
}

Drag.defaultProps = {
    dropEffect: dropEffects.All, // 设置默认的效果
};

进一步完善

到这一步,大体的功能我们都完成的七七八八了,最后还剩下一些收尾的工作。首先我们可以添加接口用来让用户可以自定义拖拽图像:

const Drag = (props) => {
    
    const image  = React.useRef(null);
    
    React.useEffect(() => {
        image.current = null;
        if (props.dragImage) {
            image.current = new Image();
            image.current.src = props.dragImage;
        }
    }, [props.dragImage]);
    
    const startDrag = ev => {
        ev.dataTransfer.setData("drag-item", props.dataItem);
        ev.dataTransfer.effectAllowed = props.dropEffect;
        // 设置图片
        if (image.current) {
    		ev.dataTransfer.setDragImage(image.current, 0, 0);
        }
    };
    
    return (
      <div draggable onDragStart={startDrag}>
        {props.children}
      </div>);
}

接着,我们再来添加样式:

// 样式
const draggingStyle = {
    opacity: 0.25,
};

const Drag = props => {
    const [isDragging, setIsDragging] = React.useState(false);
    const image = React.useRef(null);

    React.useEffect(() => {
        image.current = null;
        if (props.dragImage) {
            image.current = new Image();
            image.current.src = props.dragImage;
        }
    }, [props.dragImage]);

    const startDrag = ev => {
        setIsDragging(true);
        ev.dataTransfer.setData("drag-item", props.dataItem);
        ev.dataTransfer.effectAllowed = props.dropEffect;
        if (image.current) {
            ev.dataTransfer.setDragImage(image.current, 0, 0);
        }
    };

    // 拖拽结束时,添加样式
    const dragEnd = () => setIsDragging(false);

    return (
        <div style={isDragging ? draggingStyle : {}} draggable onDragStart={startDrag} onDragEnd={dragEnd}>
            {props.children}
        </div>
    );
};

最后,需要注意的是,如果需要处理移动端的兼容性,那么可以使用如下库:

参考资料

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
14
获赞与收藏
130

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消