对 Ant-design 的 Drawer 组件 增加拖拽效果

打印 上一主题 下一主题

主题 567|帖子 567|积分 1709

写在前面

Drawer 组件在日常开发中利用的频率照旧挺高的,一样平常环境下,都可以满意我们的日常利用。
近来笔者看到这样的一个需求,就是把 Drawer 改造成可拖拽的效果,由于改成可拖拽的就可以对里面的内容进行自适应的宽度改变,比如 Echarts 图表类的比力适合,拖拽的宽一些可视性会更好。
那话不多少,我们来想一下怎么实现这个功能。
思路

首先先来看一下 Drawer 组件,如果我们想拖拽一定是沿着左边的线去拖拽,大概可以加个图标固定拖拽的位置都可以。

那思路也很简单:
要达到拖拽效果,肯定要满意的是我们的拖拽的距离等于 Drawer 的宽度,只要满意这个条件,那拖拽效果就成立了。 
那现在的问题就转变为怎么求拖拽的距离了。
拖拽距离 = 终止位置 - 起始位置
当按下时,需要记载起始位置,移动过程中记载实时变革的X轴坐标即可。
有了大概思路,现在我们来实现吧。

加边拖动


首先我们需要确定按下的元素,已知我们需要按着左边的边进行移动,也可以用图标代替,现在我们先利用边,图标同理,由于需要给边添加相关变乱,这里我们需要添加一个假的边进行实现。
这是我们的原始代码,现在先加边
  1.       <Drawer
  2.         title="Draggable Drawer"
  3.         placement="right"
  4.         onClose={onClose}
  5.         visible={visible}
  6.         getContainer={false}
  7.       >
  8.         <p>Some contents...</p>
  9.         <p>Some contents...</p>
  10.         <p>Some contents...</p>
  11.       </Drawer>
复制代码
加边的思路是在 Drawer 组件内增加一个容器,将容器的边和 Drawer 组件的边重合即可,此时需要设置 Drawer 组件的 padding 为 0,要否则不会重叠。
  1.       <Drawer
  2.         title="Draggable Drawer"
  3.         placement="right"
  4.         onClose={onClose}
  5.         visible={visible}
  6.         getContainer={false}
  7.         bodyStyle={{ padding: 0 }}
  8.       >
  9.         <div className="drawerContent">
  10.           <p>Some contents...</p>
  11.           <p>Some contents...</p>
  12.           <p>Some contents...</p>
  13.         </div>
  14.       </Drawer>
复制代码
赤色边框就是我们的容器,可以看到已经重叠了 
 

现在还不行,由于我们只想拖拽左边的线,不能对整个容器增加变乱,也很简单,再加一条竖线放在左边进行重叠即可。
代码如下:
  1.       <Drawer
  2.         title="Draggable Drawer"
  3.         placement="right"
  4.         onClose={onClose}
  5.         visible={visible}
  6.         getContainer={false}
  7.         bodyStyle={{ padding: 0 }}
  8.         width="auto"
  9.       >
  10.         <div className="drawerContent">
  11.           <div className="dragLine"></div>
  12.           <p>Some contents...</p>
  13.           <p>Some contents...</p>
  14.           <p>Some contents...</p>
  15.         </div>
  16.       </Drawer>
复制代码
为了展示,现在有些覆盖,背面效果调整宽度和配景色即可。

变乱 

现在边已经好了,我们接下来只需要对边增加相关变乱即可。
鼠标按下

当鼠标按下时,记载起始位置,拿到 Drawer 组件内容器的开始宽度,同时增加移动和抬起变乱,由于背面需要移动元素的。
这里表明一下为什么是拿到 Drawer 组件内容器的宽度,而不是直接设置 Drawer 的宽度,这里我利用的是将 Drawer 的宽度为 auto,通过容器的宽度去撑起 Drawer 的宽度,你也可以直接设置Drawer 的宽度,但是移动过程中会有一些问题,这里就不做演示了,感兴趣的可以试试。
  1.   const startX = useRef(0)
  2.   const startWidth = useRef(0)
  3.   const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
  4.     startX.current = e.clientX
  5.     startWidth.current = drawerRef.current?.getBoundingClientRect().width || 0
  6.     document.addEventListener('mousemove', onMouseMove)
  7.     document.addEventListener('mouseup', onMouseUp)
  8.   }
复制代码
鼠标移动

鼠标按下记载起始位置,移动时就需要进行计算了,实时得到移动的距离。
表明一下 newWidth 就是新的宽度,正常环境下应该等于:容器的宽度 + 移动的距离
但也有移动到左侧制止,再往右侧移动的
由于是先往左侧移动,移动得到的差值是负数,所以直接减就行。
如果是移动到左侧制止,再往右侧移动的,那差值是正值,直接减就行了。
  1.   const onMouseMove = (e: MouseEvent) => {
  2.     console.log('e.clientX : ', e.clientX)
  3.     const newWidth = startWidth.current - (e.clientX - startX.current)
  4.     if (drawerRef.current) {
  5.       drawerRef.current.style.width = `${newWidth > 0 ? newWidth : 0}px`
  6.     }
  7.   }
复制代码
 鼠标抬起

当鼠标抬起时,只需要移除相关变乱即可。
  1.   const onMouseUp = useCallback() => {
  2.     document.removeEventListener('mousemove', onMouseMove)
  3.     document.removeEventListener('mouseup', onMouseUp)
  4.   }
复制代码
初始效果


bug修复

可以看到效果基本实现了,但是有一些小bug
第一:拖拽过程有笔墨选择效果,不好
办理办法:只需要在鼠标按下变乱里增加阻止默认变乱,阻止捕捉和冒泡阶段中当前变乱的进一步传播。
  1.   const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
  2.     e.preventDefault()
  3.     e.stopPropagation()
  4.     startX.current = e.clientX
  5.     startWidth.current = drawerRef.current?.getBoundingClientRect().width || 0
  6.     document.addEventListener('mousemove', onMouseMove)
  7.     document.addEventListener('mouseup', onMouseUp)
  8.   }
复制代码
第二:移动到最右侧时,里面的内容容器的宽度还在减小
办理办法:给容器设置宽度以及,最小宽度即可
  1. .drawerContent {
  2.   min-width: 200px;
  3.   width: 200px;
  4.   height: 100%;
  5.   border: 1px solid red;
  6.   position: relative;
  7. }
复制代码
再来看看效果,现在上面的问题都没了,还不错

图标拖动

上面我们是通过加边实现的,但每每实际需求中,可能会有其他的方式要求,比如加一个固定的图标大概UI设计的进行拖动,这里我们就加个简单的图标,变乱和上面一致。
  1.       <Drawer
  2.         title="Draggable Drawer"
  3.         placement="right"
  4.         onClose={onClose}
  5.         visible={visible}
  6.         getContainer={false}
  7.         width="auto"
  8.         bodyStyle={{ padding: 0 }}
  9.       >
  10.         <div ref={drawerRef} className="drawerContent">
  11.           <DragOutlined className="dragLine" style={{ fontSize: '50px' }}
  12.           onMouseDown={onMouseDown}/>
  13.           <p>Some contents...</p>
  14.           <p>Some contents...</p>
  15.           <p>Some contents...</p>
  16.         </div>
  17.       </Drawer>
复制代码
 同时设置样式
  1. .dragLine {
  2.     cursor: ew-resize;
  3.     width: 50px;
  4.     height: 50px;
  5.     position: absolute;
  6.     top: 43%;
  7.     left: -25px;
  8. }
复制代码
但是发现只表现了一半,另一半被遮挡住了
只需要设置Drawer组件的overflow:visible即可
  1. bodyStyle={{ padding: 0, overflow: 'visible' }}
复制代码
效果一致,各人可以根据本身需求进行更改

Drawer在左侧时

上面说的是 Drawer 在右侧时,现在说一下在左侧时,原理一样,只需要更改按压元素的位置即可移动时计算的距离即可。
定位改到右侧
  1. .dragLine {
  2.   cursor: ew-resize;
  3.   position: absolute;
  4.   top: 50%;
  5.   right: -25px;
  6. }
复制代码
往右移动改为加即可
  1. const newWidth = startWidth.current + (e.clientX - startX.current)
复制代码
效果一致

总结

实现这个需求首先确定按下元素的位置,为按下元素添加相应变乱,根据移动的距离动态赋值内容容器的宽度,再根据Drawer的width:auto,自动撑开Drawer组件即可。
当然实现过程中也有其他小 bug,比如选中笔墨,设置最小宽度等等,其实还有小问题,文章中并没有出现,就是如果里面的内容时 iframe 引入的话,会导致移动变乱失效,这个会单独写一篇文章,重要方便有雷同问题的朋友进行搜索办理问题。
全部源码

  1. import React, { useState, useRef, useCallback, useEffect } from 'react'
  2. import { Drawer, Button } from 'antd'
  3. import '../DraggableDrawer.css' // 为了额外的样式
  4. import { DragOutlined } from '@ant-design/icons'
  5. export default function DrawDemo() {
  6.   const [visible, setVisible] = useState(false)
  7.   const drawerRef = useRef<HTMLDivElement>(null)
  8.   const startX = useRef(0)
  9.   const startWidth = useRef(0)
  10.   const showDrawer = () => {
  11.     setVisible(true)
  12.   }
  13.   const onClose = () => {
  14.     setVisible(false)
  15.   }
  16.   const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
  17.     e.preventDefault()
  18.     e.stopPropagation()
  19.     startX.current = e.clientX
  20.     startWidth.current = drawerRef.current?.getBoundingClientRect().width || 0
  21.     console.log('startWidth.current: ', startWidth.current)
  22.     document.addEventListener('mousemove', onMouseMove)
  23.     document.addEventListener('mouseup', onMouseUp)
  24.   }
  25.   const onMouseMove = (e: MouseEvent) => {
  26.     const newWidth = startWidth.current - (e.clientX - startX.current)
  27.     if (drawerRef.current) {
  28.       drawerRef.current.style.width = `${newWidth > 0 ? newWidth : 0}px`
  29.     }
  30.   }
  31.   const onMouseUp = () => {
  32.     document.removeEventListener('mousemove', onMouseMove)
  33.     document.removeEventListener('mouseup', onMouseUp)
  34.   }
  35.   return (
  36.     <>
  37.       <Button type="primary" onClick={showDrawer}>
  38.         Open Draggable Drawer
  39.       </Button>
  40.       <Drawer
  41.         title="Draggable Drawer"
  42.         placement="right"
  43.         onClose={onClose}
  44.         visible={visible}
  45.         getContainer={false}
  46.         width="auto"
  47.         bodyStyle={{ padding: 0, overflow: 'visible' }}
  48.       >
  49.         <div ref={drawerRef} className="drawerContent">
  50.           <DragOutlined className="dragLine" style={{ fontSize: '50px' }} onMouseDown={onMouseDown} />
  51.           {/* <iframe src="http://localhost:8080/management"></iframe> */}
  52.           {/* <div className="dragLine" onMouseDown={onMouseDown}></div> */}
  53.         </div>
  54.       </Drawer>
  55.     </>
  56.   )
  57. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表