当前位置: 首页 > Web前端 > HTML

实现一个可拖动的列组件

时间:2023-03-28 11:52:49 HTML

初步尝试开始实现的时候,思路很简单。首先实现一个具有两列功能的组件。页面中主要有三个元素:左栏、右栏、分割线,都使用了绝对定位。实际样式预览import{FC,useState}from'react';importstylesfrom'./index.module.scss';importcnfrom'classnames';constResizableCol:FC=()=>{const[width,setWidth]=使用状态(100);return({/**左分栏*/}

{/**右分栏*/}
{/**分割线*/}
);};exportdefaultResizableCol;.container{位置:相对;高度:100%;}.divider{位置:绝对;顶部:0;底部:0;宽度:1px;高度:100%;背景色:#000;游标:col-resize;z-index:1;}.block{位置:绝对;顶部:0;底部:0;}.leftside{左:0;背景颜色:#ddd;}.rightside{右:0;background-color:#bbb;}添加交互,优化样式实现样式后,为组件添加相关交互代码,主要是为onMouseDown/onMouseMove/onMouseUp添加相应的事件处理函数其中:onMouseDown:记录用户同时,如果发现onMouseUp没有正常触发,则调用相关的处理函数handleMouseUp。onMouseMove:根据用户当前鼠标位置计算左右栏的宽度和分界线的位置。onMouseUp:清除数据。另外,onMouseMove和onMouseUp事件是由外层容器元素处理的,主要是因为当用户快速滑动鼠标时,如果鼠标离开了分界线元素,那么这两个事件就不会继续触发,因为分界线非常快。窄,只有几个像素宽,所以这种情况极有可能发生,所以这两个事件需要提升到父容器去处理。//记录点击的开始位置conststartXRef=useRef(null)//记录左列的宽度const[width,setWidth]=useState(100);//当分割线开始移动时,记录此时左列的宽度constoldWidthRef=useRef(100);//onMouseDown处理函数consthandleMouseDown=useCallback((e:React.MouseEvent)=>{if(e.button===0){if(startXRef.current!==null){handleMouseUp(e);}startXRef.current=e.clientX;}},[]);//onMouseMove处理程序consthandleMouseMove=useCallback((e:React.MouseEvent)=>{if(startXRef.current===null){return;}setWidth(e.clientX-startXRef.current+oldWidthRef.current);},[])//onMouseUp处理程序consthandleMouseUp=useCallback((e:React.MouseEvent)=>{if(e.button===0){startXRef.current=null;oldWidthRef.current=width;}},[width])返回(=null?'col-resize':'default'}}>
);同时,如果分界线元素太窄(比如1个像素),用户很难选择分界线,所以修改它的宽度为7个像素。divider{position:absolute;顶部:0;底部:0;宽度:1px;填充:03px;高度:100%;游标:col-resize;z-索引:1;&:after{显示:内联块;;位置:绝对;左:3px;顶部:0;底部:0;宽度:1px;背景色:#000;z-指数:-1;,四栏,和两栏的实现思路没有区别,同样是响应用户交互后更新多栏的宽度。同时,这次将不同列的内容改为父组件传过来的形式,所以ResizableCol现在可以接受下面的propsexportinterfaceProps{/**不同列的内容*/content:JSX.Element[];/**不同列的默认宽度*/defaultWidth?:number[];}由于逻辑上没有太大区别,所以我直接贴上ResizableCol的代码importReact,{FC,useState,useCallback,useRef}from'react';从'./index.module.scss'导入样式;从'./type'导入{Props};constDefaultWidth=100;functionisValidWidth(width:number){returnwidth>0;}functioncumsum(arr:number[],start:number,end?:number){letresult=0;for(leti=start,j=end==null?arr.length:end;i=props=>{const{content,defaultWidth}=props;constcolCount=content.length;constvalidDefaultWidth=(defaultWidth||[]).map(width=>isValidWidth(width)?width:默认值宽度);对于(leti=validDefaultWidth.length;i(空);const[widthList,setWidthList]=useState(validDefaultWidth);constoldWidthRef=useRef(validDefaultWidth);conststartClientXRef=useRef<数字|null>(null)consthandleMouseDown=useCallback((e:React.MouseEvent)=>{if(e.button===0){if(startClientXRef.current!==null){handleMouseUp(e);}startClientXRef。current=e.clientX;//记录索引constdividerEl=e.targetasHTMLDivElement;constindexStr=dividerEl.dataset.index;if(!indexStr){return;}constindexNum=Number(indexStr);if(isNaN(indexNum)){return;}indexRef.current=indexNum;}},[]);consthandleMouseMove=useCallback((e:React.MouseEvent)=>{if(startClientXRef.current===null){返回;}constindexNum=indexRef.current;如果(isUndef(indexNum)){返回;}setWidthList(widthList=>{letnewWidth=e.clientX-startClientXRef.current!+oldWidthRef.current[indexNum];newWidth=Math.max(Math.min(newWidth,200),100);if(newWidth===widthList[indexNum]){returnwidthList;}constnewList=[...widthList];newList[indexNum]=newWidth;returnnewList;});},[]);consthandleMouseUp=useCallback((e:React.MouseEvent)=>{if(e.button===0){startClientXRef.current=null;oldWidthRef.current=widthList;}},[widthList])return({content.map((col,index)=>{constleft=cumsum(widthList,0,index);constwidth=widthList[索引];返回(<>{col}{index!==colCount-1?():null});})});};导出默认的ResizableCol;后续在多点的基础上,还需要增加一些组件交互的限制,比如不同列的宽度限制(上面代码限制列的宽度为100px到200px),这些限制和可能的联动不同列的宽度之间可以根据自己的需要去实现,需要考虑当前的实现在性能上是否满足要求。