我们继续我们的 React 组件库系列。本篇介绍弹出层 PopOver以及 tooltip 的实现。本文的代码展示的是主要的核心代码,全部代码见仓库:Gitee仓库,组件样式见主页。
首先,还是先确定一下一个弹出层都需要什么参数来控制呢。相信大家也都用过Tooltip之类的组件,肯定有控制显示隐藏的属性,还有展开方向等,我这里总结了一下基础的属性:
属性 | 说明 |
---|---|
visible | 受控,控制弹出层展示 |
trigger | 触发方式 |
placement | 展开方向 |
onVisibleChange | 显示事件 |
popup | 弹出层的元素 |
zIndex | 层级 |
popupClassName | 弹出层类名 |
rc-trigger是一套流行的开源库,集成了各种触发方式判断、弹出层渲染等功能。本组件库就使用该第三方库。
先引入一下:
import RcTrigger from 'rc-trigger';
然后声明一个rcTrigger的组件:
class RcTriggerWrap extends Component {
static propTypes = {
className: PropTypes.string,
popupClassName: PropTypes.string,
trueClassName: PropTypes.string,
};
render() {
const { className, popupClassName, trueClassName, ...rest } = this.props as any;
return (
<RcTrigger
className={trueClassName}
popupClassName={classnames(className, popupClassName)}
{...rest}
/>
);
}
}
接下来,用styled样式包裹一下,便于我们自定义样式:
import styled from '@emotion/styled';
export const PopoverWrap = styled(RcTriggerWrap)`
/* 放置css */
`;
这个PopoverWrap
便是我们之后要用的弹出层组件的外壳了。
弹出层的组件,其实只需要对外暴露 action
、placement
和 popup
属性就行了,所谓在导出时,我们需要再封装一下。
在组件库的index.tsx文件中,可以这样使用:
...
return <PopoverWrap
action={trigger}
popupPlacement={placement}
popupTransitionName={
transitionName || animation ? animationPrefixCls + '-' + animation : null
}
popupVisible={popup == null ? false : this.state.visible}
popup={popup}
onPopupVisibleChange={this.onVisibleChange}
popupClassName={className}
getPopupContainer={getPopupContainer}
>
{children}
</PopoverWrap>
placement?:
| 'topLeft'
| 'top'
| 'topRight'
| 'bottomLeft'
| 'bottom'
| 'bottomRight'
| 'leftTop'
| 'left'
| 'leftBottom'
| 'rightTop'
| 'right'
| 'rightBottom';
对于弹出层组件的外壳PopoverWrap,还记得上边预留的样式的空白了吗,我们这里分情况讨论。
先把外壳设置为绝对定位,这是为了配合rc-trigger的top和bottom样式,另外,想要弹出层跟随点击区域,也必须绝对定位,父元素使用相对定位。
const _prefixCls = 'dux-ui'; // 通过导入全局变量获得
export const prefixCls = _prefixCls + '-popover';
export const animationPrefixCls = prefixCls + '-animation';
export const PopoverWrap = styled(RcTriggerWrap)`
&.${prefixCls} {
// 弹出层绝对定位
position: absolute;
z-index: 1030;
display: block;
&-hidden {
display: none;
}
}
...
`;
在控制台里可以看到:
rc-trigger会在传入的popupClassName后拼接一个带有方向参数的类名,由此,我们来定义各自的样式,以左下和左上为例:
&-enter-active.${prefixCls}-placement-bottomLeft,
&-appear-active.${prefixCls}-placement-bottomLeft {
animation-name: ${slideUpIn};
animation-play-state: running;
}
&-enter-active.${prefixCls}-placement-topLeft,
&-appear-active.${prefixCls}-placement-topLeft {
animation-name: ${slideDownIn};
animation-play-state: running;
}
分别配置使用不同的动画,如果不想自定义动画,也可以不设置。
如果props中声明了动画类型,则会有一个这样的类名:
以fade为例,可以这样声明css:
const animationDuration = '0.1s';
...
&-fade {
&-enter,
&-appear,
&-leave {
animation-duration: ${animationDuration};
animation-fill-mode: both;
}
&-enter,
&-appear {
animation-name: ${fadeIn};
}
&-leave {
animation-name: ${fadeOut};
}
}
至于动画的具体写法,有很多种,这里以淡入淡出为例,实现一种作为参考:
export const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
export const fadeOut = keyframes`
from {
opacity: 1;
}
to {
opacity: 0;
}
`;
有了这些基本的配置,一个弹出层组件就能跑起来了。
测试:
const Popup = () => (
<div style={{ height: 30, border: '1px solid #ddd', background: '#fff' }}>This is a popup</div>
);
<Popover trigger={['hover']} popup={<Popup />}>
<Content>{'hover'}</Content>
</Popover>
预览效果:
成功!
Tooltip的底层,其实就是一个popover,我们来实现一下。
直接看return的结果:
<Popover
placement={placement}
popupClassName={...}
popup={popup}
/>
Tooltip不同于通用弹出层的地方在于popup的内容,他会有一个类似漫画对话框的小箭头。那我们来定义一下popup:
export const arrowWrapCls = popoverPrefixCls + '-span' + '-arrow-wrap';
export const arrowCls = popoverPrefixCls + '-span' + '-arrow-inner';
const popup = useMemo(() => {
return (
<TooltipWrap>
{arrow && (
<Arrow className={arrowWrapCls}>
<ArrowInner className={arrowCls} />
</Arrow>
)}
<ContentWrap customStyle={customStyle}>
{popup}
</ContentWrap>
</TooltipWrap>
);
}, [popup, arrow, customStyle, theme]);
可以看到其接受了几个新的参数,我们一个个来分析。
arrow
: 一个bool值,控制是否显示箭头。默认是没有的:我们来定义箭头的样式,以 top 的方向为例:
const arrowMixin = css`
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-width: 0;
border-color: transparent;
border-style: solid;
`;
export const Arrow = styled('span')`
${arrowMixin};
`;
export const ArrowInner = styled('span')`
${arrowMixin};
`;
.${arrowWrapCls}, .${arrowCls} {
margin-left: -${arrowWidth};
border-width: ${arrowWidth} ${arrowWidth} 0 ${arrowWidth};
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
}
.${arrowWrapCls} {
bottom: -${arrowWidth};
}
.${arrowCls} {
bottom: ${borderWidth};
}
可以看到,用一个绝对定位的span包裹,设置宽高是0,然后设置其上、右、左边框宽度后再设置垂直方向的反方向,即下、左、右边框为透明色,模拟一个箭头出来,效果图如下:
同理,可设置其他方向的箭头样式~~
popup
: 是弹出层元素。TooltipWrap
和ContentWrap
是styled包裹组件,本质是个div,可以自定义样式。至此,弹出层组件也讲完辣~~
阅读量:597
点赞量:0
收藏量:0