@alifd/overlay
用于生成弹层的工具类集合。
Overlay 提供了一系列组件用于创建弹层。其中包含:
Overlay 可以在页面中弹出一个浮层,封装了定位,动画及其他一些可用性的功能。Overlay 被设计为无状态的组件,其本身并不控制自己显示和隐藏的状态。
注意: 类似 canCloseby* 的配置也需要配合 onRequestClose 才能关闭弹层。
Overlay 提供了点击弹层外文档中节点隐藏该弹层的功能,如果想让某个节点点击后不隐藏弹层(如:触发弹层打开的节点),请将该节点传入 safeNode 属性。
['tl', 'bl']
,其中 tl
代表目标元素的左上方,bl
代表基准元素的左下方,所以 ['tl', 'bl']
的意思是目标元素的左上方对齐基准元素左下方。其中定位的可选值有 tl
, tc
, tr
, cl
, cc
, cr
, bl
, bc
, br
。t
为 top
的缩写,b
为 bottom
的缩写,c
为 center
的缩写,l
为 left
的缩写,r
为 right
的缩写。Popup 是对 Overlay 的封装,children 作为触发节点,弹出一个浮层,这个浮层默认情况下使用这个节点作为定位的参照对象。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
children | 弹层内容 | ReactElement | - |
visible | 是否显示弹层 | Boolean | false |
onRequestClose | 弹层请求关闭时触发事件的回调函数 | Function | () => {} |
target | 弹层定位的参照元素 | Function | ()=> document.body |
points | 弹层相对于参照元素的定位 | [point, point] | ['tl', 'bl'] |
placement | 部分 points 的简写模式 可选值: 't'(上,对应 points: ['bc', 'tc']) 'r'(右,对应 points: ['lc', 'rc']) 'b'(下,对应 points: ['tc', 'bc']) 'l'(左,对应 points: ['rc', 'lc']) 'tl'(上左,对应 points: ['bl', 'tl']) 'tr'(上右,对应 points: ['br', 'tr']) 'bl'(下左,对应 points: ['tl', 'bl']) 'br'(下右,对应 points: ['tr', 'br']) 'lt'(左上,对应 points: ['rt', 'lt']) 'lb'(左下,对应 points: ['rb', 'lb']) 'rt'(右上,对应 points: ['lt', 'rt']) 'rb'(右下,对应 points: ['lb', 'rb']) |
Enum | 'bl' |
offset | 弹层相对于 trigger 的定位的微调,接收数组[hoz, ver], 表示弹层在 left / top 上的增量 e.g. [100, 100] 表示往右、下分布偏移 100px |
Array | [0, 0] |
container | 渲染组件的容器,如果是函数需要返回 ref,如果是字符串则是该 DOM 的 id,也可以直接传入 DOM 节点 | any | - |
hasMask | 是否显示遮罩 | Boolean | false |
canCloseByEsc | 是否支持 esc 按键关闭弹层 | Boolean | true |
canCloseByOutSideClick | 点击弹层外的区域是否关闭弹层,不显示遮罩时生效 | Boolean | true |
canCloseByMask | 点击遮罩区域是否关闭弹层,显示遮罩时生效 | Boolean | true |
onOpen | 弹层打开时触发事件的回调函数 | Function | noop |
onClose | 弹层关闭时触发事件的回调函数 | Function | noop |
beforePosition | 弹层定位完成前触发的事件 | Function | noop |
onPosition | 弹层定位完成时触发的事件 签名: Function(config: Object) => void 参数: config: {Object} 定位的参数 config.config.points: {Array} 对齐方式,如 ['cc', 'cc'](如果开启 needAdjust,可能和预先设置的 points 不同) config.style.top: {Number} 距离视口顶部距离 config.style.left: {Number} 距离视口左侧距离 |
Function | noop |
autoFocus | 弹层打开时是否让其中的元素自动获取焦点 | Boolean | false |
autoAdjust | 当弹层由于页面滚动等情况不在可视区域时,是否自动调整定位以出现在可视区域 | Boolean | true |
autoHideScrollOverflow | 当 trigger 外面有滚动条,滚动到不可见区域后隐藏弹窗 | Boolean | true |
cache | 隐藏时是否保留子节点 | Boolean | false |
safeNode | 安全节点,当点击 document 的时候,如果包含该节点则不会关闭弹层,如果是函数需要返回 ref,如果是字符串则是该 DOM 的 id,也可以直接传入 DOM 节点,或者以上值组成的数组 | any | - |
wrapperClassName | 弹层的根节点的样式类 | String | - |
wrapperStyle | 弹层的根节点的内联样式 | Object | - |
继承 Overlay 的 API,除非特别说明
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
children | 触发弹层显示或隐藏的元素 | ReactNode | - |
overlay | 弹层内容 | ReactElement | - |
triggerType | 触发弹层显示或隐藏的操作类型,可以是 'click','hover','focus',或者它们组成的数组,如 ['hover', 'focus'] | String/Array | 'hover' |
triggerClickKeycode | 当 triggerType 为 click 时才生效,可自定义触发弹层显示的键盘码 | Number/Array | [KEYCODE.SPACE, KEYCODE.ENTER] |
visible | 弹层当前是否显示 | Boolean | - |
defaultVisible | 弹层默认是否显示 | Boolean | false |
onVisibleChange | 弹层显示或隐藏时触发的回调函数 签名: Function(visible: Boolean, type: String, e: Object) => void 参数: visible: {Boolean} 弹层是否显示 type: {String} 触发弹层显示或隐藏的来源 fromTrigger 表示由 trigger 的点击触发;docClick 表示由 document 的点击触发 e: {Object} DOM 事件 |
Function | noop |
disabled | 设置此属性,弹层无法显示或隐藏 | Boolean | false |
delay | 弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效 | Number | 200 |
mouseEnterDelay | 鼠标移入弹层显示的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效,优先级高于 delay | Number | - |
mouseLeaveDelay | 鼠标移出弹层隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效,优先级高于 delay | Number | - |
followTrigger | 是否跟随 trigger 滚动 | Boolean | false |
Overlay 使用。
import Overlay from '@alifd/overlay';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
visible2: false,
visible3: false,
};
}
onClick = () => {
this.setState({
visible: !this.state.visible,
});
};
onClose = () => {
this.setState({
visible: false,
});
};
render() {
return (
<div>
<button
onClick={this.onClick}
ref={(ref) => {
this.btn = ref;
}}
>
Toggle visible
</button>
<Overlay
visible={this.state.visible}
points={['tl', 'tr']}
offset={[4, 0]}
target={() => this.btn}
safeNode={() => this.btn}
onRequestClose={this.onClose}
onOpen={() => console.log(/open/)}
onClose={() => console.log(/close/)}
>
<div
style={{
width: 200,
height: 200,
background: '#999',
borderRadius: 2,
boxShadow: '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
/>
</Overlay>
<button style={{ marginLeft: 8 }} onClick={() => this.setState({ visible2: true })}>
Fixed FullScreen
</button>
<Overlay
visible={this.state.visible2}
onRequestClose={() => this.setState({ visible2: false })}
disableScroll
fixed
offset={['calc(50vw - 90vw/2)', 'calc(50vh - 80vh/2)']}
>
<div
style={{
width: '90vw',
height: '80vh',
background: '#999',
boxShadow: '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
hello world
</div>
</Overlay>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
fixed 模式的dialog,带有遮罩的弹层。
import { useState, useRef } from 'react';
import Overlay from '@alifd/overlay';
const maskStyle = {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
height: '100%',
backgroundColor: '#00000073',
};
const Demo = () => {
const [visible, setVisible] = useState(false);
const [visible2, setVisible2] = useState(false);
const dialogRef = useRef(null);
const onClose = (e) => {
if (e.type === 'click' && dialogRef.current && dialogRef.current.contains(e.target)) {
return;
}
setVisible(false);
};
const onClose2 = (e) => {
setVisible2(false);
};
return (
<div>
<button onClick={() => setVisible(true)}> 超出滚动 </button>
<button onClick={() => setVisible2(true)} style={{ marginLeft: 8 }}>
{' '}
内部滚动{' '}
</button>
<Overlay
visible={visible}
maskClassName="next-overlay-mask"
maskStyle={maskStyle}
hasMask
canCloseByMask
fixed
offset={[0, 0]}
onRequestClose={onClose}
autoFocus
>
<div
style={{
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
overflow: 'auto',
}}
onClick={onClose}
>
<div
style={{
position: 'relative',
top: 100,
width: '100vw',
pointerEvents: 'none',
paddingBottom: 24,
}}
>
<div
role="dialog"
ref={dialogRef}
style={{
pointerEvents: 'auto',
margin: '0 auto',
width: 500,
background: '#fff',
borderRadius: 2,
boxShadow:
'0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
with long content
<br />
<input />
<div style={{ padding: '1400px 8px 8px 8px' }}>
<button onClick={onClose}>ok</button>
<button onClick={onClose} style={{ marginLeft: 8 }}>
cancel
</button>
</div>
</div>
</div>
</div>
</Overlay>
<Overlay
visible={visible2}
maskClassName="next-overlay-mask"
maskStyle={maskStyle}
hasMask
canCloseByMask
fixed
autoFocus
offset={[0, 100]}
onRequestClose={onClose2}
>
<div
style={{
position: 'relative',
width: '100vw',
pointerEvents: 'none',
}}
>
<div
role="dialog"
style={{
pointerEvents: 'auto',
margin: '0 auto',
width: 500,
maxHeight: 'calc(100vh - 120px)',
overflow: 'auto',
background: '#fff',
borderRadius: 2,
boxShadow: '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
<p>with long content</p>
<input />
<div style={{ padding: '1400px 8px 8px 8px' }}>
<button onClick={onClose}>ok</button>
<button onClick={onClose} style={{ marginLeft: 8 }}>
cancel
</button>
</div>
</div>
</div>
</Overlay>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
Popup
是对 Overlay
的封装,它接收某个节点作为触发节点,弹出一个浮层,这个浮层默认情况下使用这个节点作为定位的参照对象。
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const FunctionalOverlay = (props) => (
<span {...props} style={style}>
Hello World From Popup!
</span>
);
const FunctionalButton = (props) => (
<button style={{ border: '4px solid' }} {...props}>
Open
</button>
);
ReactDOM.render(
<div>
<Popup overlay={<FunctionalOverlay />} triggerType="click">
<FunctionalButton />
</Popup>
<br />
<br />
<Popup overlay={<FunctionalOverlay />} triggerType="click" triggerClickKeyCode={40}>
<input placeholder="Use Down Arrow to open" />
</Popup>
</div>,
mountNode
);
通过 triggerType
属性设置触发方式。
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const overlay = (triggerType) => (
<span style={style} className="overlay">
{triggerType} to open!
</span>
);
const handleVisibleChange = (visible) => {
console.log(visible);
};
ReactDOM.render(
<div>
<Popup overlay={overlay('click')} triggerType="click">
<button>click</button>
</Popup>
<Popup overlay={overlay('hover')} triggerType="hover">
<button style={{ marginLeft: 16 }}>hover</button>
</Popup>
<Popup overlay={overlay('focus')} triggerType="focus">
<button style={{ marginLeft: 16 }}>focus</button>
</Popup>
<br />
<br />
<Popup overlay={overlay('hover')} triggerType="hover">
<span>
<button disabled style={{ pointerEvents: 'none' }}>
disabled hover
</button>
</span>
</Popup>
</div>,
mountNode
);
.overlay-demo {
width: 300px;
height: 100px;
padding: 10px;
border: 1px solid #eee;
background: #ffffff;
box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.15);
}
.next-btn:not(last-child) {
margin-right: 20px;
}
展示了 Popup
受控显示隐藏的用法。
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const DemoOverlay = React.forwardRef((props, ref) => {
return (
<span {...props} style={{ ...style, ...props.style }} ref={ref}>
Hello World From Popup!
</span>
);
});
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
visible3: false,
};
}
onVisibleChange = (visible) => {
this.setState({
visible,
});
};
onGroupVisibleChange = (groupVisible) => {
this.setState({
groupVisible,
});
};
render() {
return (
<div>
<div>
<Popup
overlay={<DemoOverlay />}
triggerType="click"
visible={this.state.visible}
onVisibleChange={this.onVisibleChange}
>
<button>Open</button>
</Popup>
</div>
<br />
<div>
<Popup
overlay={
<DemoOverlay
ref={(ref) => {
this.overlay1 = ref;
}}
/>
}
triggerType="click"
visible={this.state.groupVisible}
safeNode={[() => this.btn2, () => this.overlay2]}
onVisibleChange={this.onGroupVisibleChange}
>
<button
style={{ marginRight: '50px' }}
ref={(ref) => {
this.btn1 = ref;
}}
>
Paired Popup 1
</button>
</Popup>
<Popup
overlay={
<DemoOverlay
ref={(ref) => {
this.overlay2 = ref;
}}
/>
}
triggerType="click"
visible={this.state.groupVisible}
safeNode={[() => this.btn1, () => this.overlay1]}
onVisibleChange={this.onGroupVisibleChange}
>
<button
ref={(ref) => {
this.btn2 = ref;
}}
>
Paired Popup 2
</button>
</Popup>
</div>
<br />
<div>
<Popup
target={() => this.divref}
overlay={<DemoOverlay />}
visible={this.state.visible3}
safeNode={() => this.btn3}
onVisibleChange={(visible) => this.setState({ visible3: visible })}
></Popup>
<button
onClick={() => this.setState({ visible3: true })}
ref={(ref) => {
this.btn3 = ref;
}}
>
target
</button>
<div
ref={(ref) => (this.divref = ref)}
style={{
width: 20,
height: 20,
background: 'blue',
display: 'inline-block',
marginLeft: 100,
}}
/>
</div>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
显示如何创建可悬停和单击的弹出窗口。
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
class Demo extends React.Component {
state = {
clicked: false,
hovered: false,
};
hide = () => {
this.setState({
clicked: false,
hovered: false,
});
};
handleHoverChange = (visible) => {
this.setState({
hovered: visible,
clicked: false,
});
};
handleClickChange = (visible) => {
this.setState({
clicked: visible,
hovered: false,
});
};
render() {
const hoverContent = <div style={style}>This is hover content.</div>;
const clickContent = <div>This is click content.</div>;
return (
<>
<Popup
style={{ width: 500 }}
overlay={hoverContent}
triggerType="hover"
visible={this.state.hovered}
onVisibleChange={this.handleHoverChange}
>
<button ref={(ref) => (this.btn = ref)} onClick={() => this.handleClickChange(true)}>
Hover and click / 悬停并单击
</button>
</Popup>
<Popup
target={() => this.btn}
overlay={
<div style={style}>
{clickContent}
<button onClick={this.hide}>Close</button>
</div>
}
visible={this.state.clicked}
onVisibleChange={this.handleClickChange}
></Popup>
</>
);
}
}
ReactDOM.render(<Demo />, mountNode);
有弹层嵌套需求时,请使用 container 属性将第二个弹层渲染到第一个弹层内部, 这样不会因为点击第二个弹窗导致第一个弹窗消失。
import { useState } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const Demo = () => {
const [followTrigger, setFollowTrigger] = useState(false);
const overlay2 = (
<div style={{ ...style }}>
<Popup
overlay={
<div style={style}>
<p>
挂载容器没有overflow:hidden,
依然超出容器展示,不会因为挂载容器小自动订正位置,viewport 任然为 body
</p>
</div>
}
followTrigger={followTrigger}
>
<button>Open third overlay</button>
</Popup>
<p>Hello World From First Overlay!</p>
</div>
);
const overlay = (
<div style={{ ...style }}>
<Popup overlay={overlay2} followTrigger={followTrigger}>
<button>Open second overlay</button>
</Popup>
<p>Hello World From First Overlay!</p>
</div>
);
return (
<div>
followTrigger:{' '}
<input
type="checkbox"
checked={followTrigger}
onChange={(e) => setFollowTrigger(e.target.checked)}
/>
<br />
<br />
<Popup overlay={overlay}>
<button>Open first overlay</button>
</Popup>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
通过 placement
可以自定义对齐方式。
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
position: 'relative',
height: 150,
margin: 50,
border: '1px solid #eee',
textAlign: 'center',
};
ReactDOM.render(
<div id="containerId" style={style}>
<Overlay
target="containerId"
container={() => document.getElementById('containerId')}
visible
points={['br', 'tl']}
>
<button>br tl</button>
</Overlay>
<Overlay target="containerId" visible points={['tc', 'tc']}>
<button>tc tc</button>
</Overlay>
<Overlay target="containerId" visible points={['bl', 'tr']}>
<button>bl tr</button>
</Overlay>
<Overlay target="containerId" visible points={['cr', 'cr']}>
<button>cr cr</button>
</Overlay>
<Overlay target="containerId" visible points={['br', 'br']}>
<button>br br</button>
</Overlay>
<Overlay target="containerId" visible points={['tc', 'bc']}>
<button>tc bc</button>
</Overlay>
<Overlay target="containerId" visible points={['bl', 'bl']}>
<button>bl bl</button>
</Overlay>
<Overlay target="containerId" visible points={['cl', 'cl']}>
<button>cl cl</button>
</Overlay>
<Overlay target="containerId" visible points={['cc', 'cc']}>
<button>cc cc</button>
</Overlay>
</div>,
mountNode
);
能够根据空间大小自动更换 placement
若调整后位置始终不符合预期,可能是渲染过程中overlay内容宽度发生了变化导致计算错误,可以尝试固定overlay内容宽度来解决
import { useState } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 200,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const App = () => {
const [position, setPosition] = useState({});
return (
<div
style={{
position: 'relative',
height: 200,
padding: 20,
border: '1px solid #eee',
overflow: 'auto',
}}
>
<div>
<Popup
overlay={<div style={style}>上面空间不够</div>}
placement="tl"
triggerType="click"
followTrigger
>
<button>TL-> BL</button>
</Popup>
<Popup
overlay={<div style={style}>上面空间不够</div>}
placement="t"
triggerType="click"
followTrigger
>
<button style={{ marginLeft: 10 }}>T -> B</button>
</Popup>
<Popup
overlay={<div style={style}>上面空间不够</div>}
placement="tr"
triggerType="click"
followTrigger
>
<button style={{ marginLeft: 10 }}>TR -> BR</button>
</Popup>
<Popup
overlay={<div style={style}>上面空间不够</div>}
placement="tl"
triggerType="click"
followTrigger
>
<button style={{ marginTop: 0, float: 'right', right: 0 }}>TL-> BR</button>
</Popup>
</div>
<br />
<div>
<Popup
overlay={<div style={style}>左边空间不够</div>}
placement="lt"
triggerType="click"
followTrigger
>
<button style={{ marginTop: 0 }}>LT-> RT</button>
</Popup>
<Popup
overlay={<div style={style}>右边空间不够</div>}
placement="rt"
triggerType="click"
followTrigger
>
<button style={{ float: 'right', right: 0 }}>RT-> LT</button>
</Popup>
<br />
<br />
<Popup
overlay={<div style={style}>左边空间不够</div>}
placement="lb"
triggerType="click"
followTrigger
>
<button style={{ marginTop: 0 }}>LB-> RB</button>
</Popup>
<Popup
overlay={<div style={style}>右边空间不够</div>}
placement="rb"
triggerType="click"
followTrigger
>
<button style={{ float: 'right', right: 0 }}>RB-> LB</button>
</Popup>
<br />
<br />
<Popup
overlay={<div style={style}>左边、下边空间不够</div>}
placement="l"
triggerType="click"
followTrigger
>
<button style={{ marginTop: 0 }}>L-> R</button>
</Popup>
<Popup
overlay={<div style={style}>右边空间不够</div>}
placement="r"
triggerType="click"
followTrigger
>
<button style={{ float: 'right', right: 0 }}>R-> L</button>
</Popup>
</div>
<br />
<div>
<Popup
overlay={<div style={style}>下边/左边空间都不够</div>}
placement="b"
triggerType="click"
followTrigger
>
<button>B -> T</button>
</Popup>
<Popup
overlay={<div style={style}>下边/左边空间都不够</div>}
placement="br"
triggerType="click"
followTrigger
>
<button style={{ marginLeft: 10 }}>BR -> TL</button>
</Popup>
<Popup
overlay={<div style={style}>右边空间不够</div>}
placement="bl"
triggerType="click"
followTrigger
>
<button style={{ float: 'right', right: 0 }}>BL-> TR</button>
</Popup>
</div>
</div>
);
};
ReactDOM.render(<App />, mountNode);
带有遮罩的弹层。
import { useState, useRef } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 150,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const Demo = () => {
const [visible, setVisible] = useState(false);
const [visible2, setVisible2] = useState(false);
const [position, setPosition] = useState({});
const [position2, setPosition2] = useState({});
const [position3, setPosition3] = useState({});
const dialogRef = useRef(null);
const onClose = (e) => {
console.log(/onclick/, e.target);
if (dialogRef.current && dialogRef.current.contains(e.target)) {
return;
}
setVisible(false);
};
const onClose2 = (e) => {
setVisible2(false);
};
return (
<div>
<button onClick={() => setVisible(true)}> 超出滚动 </button>
<button onClick={() => setVisible2(true)} style={{ marginLeft: 8 }}>
{' '}
内部滚动{' '}
</button>
{/*<div style={{position: 'fixed', top: 200, left: 300}}>
<Popup
overlay={<span style={style}>left: {position3.left} top: {position3.top}</span>}
onPosition={({style}) => setPosition3(style)}
>
<button>打开后滚动试试</button>
</Popup>
</div>*/}
<Overlay
visible={visible}
maskClassName="next-overlay-mask"
maskStyle={{
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
height: '100%',
backgroundColor: '#00000073',
}}
hasMask
canCloseByMask
fixed
offset={[0, 0]}
onRequestClose={onClose}
>
<div
style={{
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
overflow: 'auto',
}}
onClick={onClose}
>
<div
style={{
position: 'relative',
top: 100,
width: '100vw',
pointerEvents: 'none',
paddingBottom: 24,
}}
>
<div
role="dialog"
ref={dialogRef}
style={{
pointerEvents: 'auto',
margin: '0 auto',
width: 500,
background: '#fff',
borderRadius: 2,
boxShadow:
'0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
<Popup
overlay={<span style={style}>{position.transform}</span>}
onPosition={({ style }) => setPosition(style)}
>
<button>打开后滚动试试</button>
</Popup>
<div style={{ padding: '1400px 8px 8px 8px' }}>
<button onClick={onClose}>ok</button>
<button onClick={onClose} style={{ marginLeft: 8 }}>
cancel
</button>
</div>
</div>
</div>
</div>
</Overlay>
<Overlay
visible={visible2}
maskClassName="next-overlay-mask"
maskStyle={{
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
height: '100%',
backgroundColor: '#00000073',
}}
hasMask
canCloseByMask
fixed
offset={[0, 100]}
onRequestClose={onClose2}
>
<div
style={{
position: 'relative',
width: '100vw',
pointerEvents: 'none',
}}
>
<div
role="dialog"
style={{
pointerEvents: 'auto',
margin: '0 auto',
width: 500,
maxHeight: 'calc(100vh - 120px)',
overflow: 'auto',
background: '#fff',
borderRadius: 2,
boxShadow: '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
<Popup
overlay={<span style={style}>{position2.transform}</span>}
onPosition={({ style }) => setPosition2(style)}
>
<button>打开后滚动试试</button>
</Popup>
<div style={{ padding: '1400px 8px 8px 8px' }}>
<button onClick={onClose}>ok</button>
<button onClick={onClose} style={{ marginLeft: 8 }}>
cancel
</button>
</div>
</div>
</div>
</Overlay>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
遇到有 overflow 滚动的弹窗,会自动监听滚动实时弹窗计算位置。触发器消失会自动隐藏
import { useState } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 500,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const App = () => {
const [position, setPosition] = useState({});
const [position2, setPosition2] = useState({});
return (
<div
style={{
position: 'relative',
height: 150,
padding: 50,
border: '1px solid #eee',
overflow: 'auto',
}}
>
<Popup
overlay={
<div style={style}>
父容器有滚动条,弹窗top自动跟随变化来保持位置不变: {JSON.stringify(position)}
</div>
}
placement="bl"
onPosition={({ style }) => {
setPosition(style);
}}
>
<button style={{ marginTop: 10 }}>Open1</button>
</Popup>
<br />
<Popup
overlay={
<div style={style}>
父容器有滚动条,弹窗top自动跟随变化来保持位置不变: {JSON.stringify(position2)}
</div>
}
placement="bl"
onPosition={({ style }) => {
setPosition2(style);
}}
>
<button style={{ marginTop: 120 }}>Open2</button>
</Popup>
<div style={{ height: 300, width: 1200 }} />
</div>
);
};
ReactDOM.render(<App />, mountNode);
可以通过 container 把弹窗挂到指定位置,这样性能更好不会一直计算位置,弹窗也不会超出
import { useState } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 500,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const App = () => {
const [position, setPosition] = useState({});
return (
<div
style={{
position: 'relative',
height: 150,
padding: 30,
border: '1px solid #eee',
overflow: 'auto',
}}
>
<Popup
overlay={
<div style={style}>
把节点挂载在父容器下可以避免top一直计算,提高性能: {JSON.stringify(position)}
</div>
}
placement="bl"
triggerType="click"
container={(target) => target.parentNode}
onPosition={({ style }) => {
setPosition(style);
}}
>
<button style={{ marginTop: 30 }}>Open</button>
</Popup>
<div style={{ height: 300, width: 1200 }} />
</div>
);
};
ReactDOM.render(<App />, mountNode);
Overlay 使用。
import Overlay from '@alifd/overlay';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
};
}
onRequestClose = (e) => {
// e.preventDefault();
// e.stopPropagation();
console.log(/onRequestClose/);
this.setState({
visible: false,
});
};
show = (e) => {
console.log(/show/, e);
this.setState({
visible: true,
});
};
render() {
return (
<div style={{ height: 300, width: 300, background: '#999' }} onClick={this.show}>
<button id="clickevent">click</button>
<Overlay
target={() => document.documentElement}
visible={this.state.visible}
fixed
points={['cc', 'cc']}
offset={['40%', 100]}
onRequestClose={this.onRequestClose}
onOpen={() => console.log(/onOpen/)}
onClose={() => console.log(/onClose/)}
>
<div
style={{
width: 200,
height: 200,
background: '#999',
borderRadius: 2,
boxShadow: '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d',
}}
>
<button onClick={this.onRequestClose}>close</button>
</div>
</Overlay>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
trigger 动态发生变化的时候需要能够准确获取
import { useState, useEffect } from 'react';
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const overlay = <span style={style}>Hello World From Popup!</span>;
const DyncButton = (props) => {
const [disabled, setDisabled] = useState(true);
useEffect(() => {
setTimeout(() => {
setDisabled(false);
}, 100);
}, []);
if (disabled) {
return (
<span>
<button {...props} disabled>
Disabled button
</button>
</span>
);
}
return <button {...props}>Open</button>;
};
const Demo = () => {
return (
<div>
<Popup overlay={overlay} triggerType="click">
<DyncButton />
</Popup>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
target 动态变化
import Overlay from '@alifd/overlay';
let idx = 0;
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
target: () => this.btn0,
};
}
next = (idx) => {
switch (idx) {
case 0:
this.setState({
target: () => this.btn0,
});
break;
case 1:
this.setState({
target: () => this.btn1,
});
break;
case 2:
this.setState({
target: () => this.btn2,
});
break;
}
};
onClick = () => {
this.setState({
visible: !this.state.visible,
});
};
onClose = () => {
this.setState({
visible: false,
});
};
render() {
return (
<div>
<button onClick={this.onClick} ref={(ref) => (this.btn0 = ref)}>
First
</button>
<button onClick={this.onClick} ref={(ref) => (this.btn1 = ref)} style={{ marginLeft: 500 }}>
Second
</button>
<br />
<button
onClick={this.onClick}
ref={(ref) => (this.btn2 = ref)}
style={{ marginLeft: 500, marginTop: 400 }}
>
Third
</button>
<Overlay placement="b" visible={this.state.visible} target={this.state.target}>
<span style={{ width: 300, height: 200, border: '1px solid' }}>
Hello World From Overlay!
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<button style={{ marginLeft: 200 }} onClick={() => this.next(++idx % 3)}>
next
</button>
</span>
</Overlay>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
展示 autoHideScrollOverflow
的用法
import Overlay from '@alifd/overlay';
const { Popup } = Overlay;
const style = {
width: 400,
height: 100,
padding: 10,
background: '#fff',
borderRadius: 2,
boxShadow: '2px 2px 20px rgba(0, 0, 0, 0.15)',
};
const FunctionalOverlay = (props) => (
<span {...props} style={style}>
Hello World From Popup!
</span>
);
const FunctionalButton = (props) => (
<button style={{ border: '4px solid' }} {...props}>
Open
</button>
);
ReactDOM.render(
<div>
<div className="scroll-box">
<div style={{ height: 50 }}></div>
<div>
<Popup overlay={<div className="my-popup">auto hide</div>} visible triggerType="click">
<button>trigger1</button>
</Popup>
<Popup
overlay={<div className="my-popup">not hide</div>}
visible
triggerType="click"
autoHideScrollOverflow={false}
>
<button style={{ marginLeft: 50 }}>trigger2</button>
</Popup>
</div>
<div style={{ height: 500 }}></div>
</div>
</div>,
mountNode
);
.scroll-box {
height: 300px;
width: 400px;
border: 1px solid #000;
overflow: auto;
}
.my-popup {
background-color: cyan;
height: 150px;
text-align: center;
line-height: 150px;
}