看一看antd 中 select/dropdown 等组件的下拉框

8,020 阅读3分钟

背景:在项目中使用 select 组件,点击 select 出现下拉框的状态时,滑动页面下拉框不跟随 select 组件滑动。也就是不固定在 select 组件的下方。 于是开始思考: antd 是怎么实现下拉框的?下拉框跟随滚动是怎么做的?

先说解决办法吧:

查看API文档:

getPopupContainer: 菜单(下拉框哈)渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。

getPopupContainer 接受一个函数作为参数,函数执行时会接受组件传给他的参数 triggerNode,返回菜单渲染的父节点。 所以可以设置:

getPopupContainer={(triggerNode) => triggerNode.parentNode}

这时候菜单渲染的父节点就是 antd 的 select 组件。 当然你可以设置为其他任意的节点为下拉框的父节点。但需要注意的是,下拉框是绝对定位的,要使滑动生效的话,最好父节点设置为相对定位(绝对定位也可,只是要注意与父节点同级的元素的位置关系)。否则下拉框会继续向上寻找定位不为 static 的元素,有可能就找到了 document.body

实现下拉组件的原理解析

在不设置 getPopupContainer 属性的时候,默认当第一次触发下拉框组件时,会在 body 节点下生成一个绝对定位的 div(下拉框的父节点):

image.png

当触发出现下拉框时(下拉框):

image.png

会根据 triggerNode 和 body 来计算 left 和 top 的值:(offsetTop/offsetLeft/offsetHeight/offsetWidth)

下拉框父节点一旦生成之后即使收起下拉框,也不会卸载这个节点(回流重绘好费性能的说!)。也就是说下拉框的父节点的位置就确定了,每次计算出来的下拉框的 left/right 也就确定了。 因此当 select 组件所在的可滑动区域和 document.body 高度不一致时,就会出现上面说的情况。下拉框不吸附。 这时候只要找到一个相对于 select 组件一起滑动的父组件(相对位置不变),将他作为下拉框组件的父组件即可。

情况1. 将下拉框组件放在select组件下:

getPopupContainer={(triggerNode) => triggerNode.parentNode}

image.png

.ant-select:{position:relative,...}

情况2. 放在相对于 select 组件一起滑动的父组件下,难点需要分析找到这个父组件。

image.png

这里就选择了 #mainContent 作为父节点,#rootContent 设置 height 为100%,根据其父元素的高度(100vh)进行计算, 因此 body 与 select 的相对位置是变化的(高度不以一致),所以不能选择 body,又因为所有 react_grid_item 都为绝对定位元素。select 所在的父元素高度为 32px,如果设置

getPopupContainer={(triggerNode) => triggerNode.parentNode}

则弹出的下拉框会被下一个 absolute 的 react_grid_item 元素挡住。所以这里选择将下拉框与 #mainContent同级

学到了什么

可以借鉴这种方法实现带有下拉框的组件,确定父组件,生成一个元素来确定下拉框的位置。