1.需求导向
1.1.需求导向
最近的项目中频繁的使用modal弹出框,虽然已经有很多库可用于在 React 中创建响应式、可访问的模态框。
但是,有时在设计ui中存在这些库无法完全满足,因此需要自定义modal是非常有必要的。
复制代码
1.2.实现功能
- 1.可以底部自定义按钮的样式,文字
- 2.可以自定义内容
- 3.使用Esc键可以关闭模态框
- 4.使用shift可以自动聚焦关闭按钮,使用Enter实现关闭
- 5.弹出modal,页面禁用
- 6.按下Shift + Tab将焦点移动到上一个可选项卡元素
2.封装思路
- 将该model组件分成两个部分,头部以及主体部分,头部包含取消,标题部分,主体部分包含取消确定按钮,中间内容部分,并且可以使用键盘Esc操作
3.安装依赖
yarn add react-dom
yarn add styled-components
yarn add react-focus-lock
复制代码
4.开始封装
4.1使用createPortal封装外部基础modal
createPortal是ReactDOMAPI 的一部分,它允许我们在父组件之外渲染 React 组件。我们通常在
根div 元素中渲染 React 应用程序,但是通过使用门户,我们也可以在根 div 之外渲染一个组件
复制代码
//Model.jsx
import {Fragment} from 'react'
import {createPortal}from 'react-dom';
import {
Wrapper,
Header,
StyledModal,
HeaderText,
CloseButton,
Content,
Backdrop,
} from './style';
const Modal = ({
isShown,
hide,
modalContent,
headerText,
closeIcon
}) => {
const modal = (
<Fragment>
<Backdrop />
<Wrapper>
<StyledModal>
<Header>
<HeaderText>{headerText}</HeaderText>
<CloseButton onClick={hide}>{closeIcon}</CloseButton>
</Header>
<Content>{modalContent}</Content>
</StyledModal>
</Wrapper>
</Fragment>
);
return isShown ? createPortal(modal, document.body) : null;
};
export default Modal
复制代码
//style.js
import styled from "styled-components";
export const Wrapper = styled.div`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 700;
width: inherit;
outline: 0;
`;
export const Backdrop = styled.div`
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgb(74, 74, 75);
z-index: 500;
`;
export const StyledModal = styled.div`
z-index: 100;
background: white;
position: relative;
margin: auto;
border-radius: 8px;
`;
export const Header = styled.div`
border-radius: 8px 8px 0 0;
display: flex;
justify-content: space-between;
padding: 0.3rem;
`;
export const HeaderText = styled.div`
text-align: center;
align-self: center;
color: lightgray;
`;
export const CloseButton = styled.button`
font-size: 0.8rem;
border: none;
border-radius: 3px;
margin-left: 0.5rem;
background-color:red;
color:#fff;
cursor: pointer;
:hover {
cursor: pointer;
}
`;
export const Content = styled.div`
padding: 10px;
max-height: 30rem;
overflow-x: hidden;
overflow-y: auto;
`;
复制代码
4.2 使用自定义hook
为了使用的模态框,这里我创建一个自定义的 React hook来管理模态的状态。可以在任何要渲染模态的组件中使用自定义钩子。
import { useState } from 'react';
export const useModal = () => {
const [isShown, setIsShown] = useState(false);
const toggle = () => setIsShown(!isShown);
return {
isShown,
toggle,
};
};
复制代码
4.3定义主体部分
模态框的主体包含两个部分,按钮和内容,封装的组件外部可以修改按钮的样式以及文字,并且可以
自定义内容。
import {Fragment} from 'react';
import { ConfirmationButtons, Message, YesButton, NoButton } from './style';
export const ConfirmationModal= (props) => {
const {message,onConfirm,onCancel,sureColor,sureHoverColor,
cancelColor,cancelHoverColor,cancelText,sureText}=props
return (
<Fragment>
<Message>{message}</Message>
<ConfirmationButtons>
<YesButton onClick={onConfirm}
sureColor={sureColor}
sureHoverColor={ sureHoverColor}
>{sureText}</YesButton>
<NoButton
onClick={onCancel}
cancelColor={cancelColor}
cancelHoverColor={cancelHoverColor}
>{cancelText}</NoButton>
</ConfirmationButtons>
</Fragment>
);
};
复制代码
//style.js
import styled from "styled-components";
export const ConfirmationButtons = styled.div`
display: flex;
width: 100%;
justify-content: space-around;
height: 20px;
`;
export const Message = styled.div`
font-size: 0.9rem;
margin-bottom: 10px;
text-align: center;
`;
export const YesButton = styled.button.attrs({
backgroundColor:props=>props.sureHoverColor || props.sureColor
})`
flex: 1;
background-color: ${props=>props.sureColor};
border: none;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
cursor: pointer;
:hover {
background-color: ${props=>props.sureHoverColor};
color: #fff;
cursor: pointer;
}
`;
export const NoButton = styled.button.attrs({
backgroundColor:props=>props.cancelColor || props.cancelHoverColor
})`
flex: 1;
background-color: ${props=>props.cancelColor};
border: none;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
cursor: pointer;
:hover {
background-color:${props=>props.cancelHoverColor};
color: #fff;
cursor: pointer;
}
`;
复制代码
4.4.添加键盘交互
为了允许用户在ESC按键时关闭模态,我们需要向我们的模态框添加一个事件键侦听器。当ESC按键被按下并显示模态时,隐藏模态框的功能将被执行。这里使用useEffect钩子来实现这一点。
const onKeyDown = (event) => {
if (event.keyCode === 27
&& isShown) {
hide();
}
};
useEffect(() => {
//禁用滚动
isShown ? (document.body.style.overflow = 'hidden') :
(document.body.style.overflow = 'unset');
//键盘操作事件
document.addEventListener('keydown', onKeyDown, false);
return () => {
document.removeEventListener('keydown', onKeyDown, false);
};
}, [isShown, onKeyDown]);
复制代码
- Esc 键关闭模态
- 按下Shift将焦点移动到模态内的下一个可选项卡元素
- 按下Shift + Tab将焦点移动到上一个可选项卡元素
- 页面禁止滚动
4.5焦点陷阱
我们清单中的最后一项是将焦点捕获在模态内。我们可以通过单击Shift或来遍历模态内的元
素Shift + Tab。当我们到达最后一个模态框内的元素时,如果我们按下 Shift,焦点将移
动到模态之外的元素。
但这不是我想要的。我想要的是当焦点到达最后一个模态框内的元素并继续使用 Shift 键遍
历时,焦点将转到第一个模态框内的元素。它就像一个循环。一旦我们到达循环的末尾,我们
就从头开始。
我是通过在模态框中获取所有可聚焦元素来实现此功能,然后循环遍历它们以捕获焦点,这里
借助了react-focus-lock.
复制代码
import {Fragment,useEffect} from 'react'
import {createPortal}from 'react-dom';
import FocusLock from 'react-focus-lock';
import {
Wrapper,
Header,
StyledModal,
HeaderText,
CloseButton,
Content,
Backdrop,
} from './style';
const Modal = ({
isShown,
hide,
modalContent,
headerText,
closeIcon
}) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const onKeyDown = (event) => {
if (event.keyCode === 27
&& isShown) {
hide();
}
};
useEffect(() => {
isShown ? (document.body.style.overflow = 'hidden') : (document.body.style.overflow = 'unset');
document.addEventListener('keydown', onKeyDown, false);
return () => {
document.removeEventListener('keydown', onKeyDown, false);
};
}, [isShown, onKeyDown]);
const modal = (
<Fragment>
<Backdrop onClick={hide} />
//这里使用FocusLock包裹
<FocusLock>
<Wrapper aria-modal aria-labelledby={headerText} tabIndex={-1} role="dialog">
<StyledModal >
<Header>
<HeaderText>{headerText}</HeaderText>
<CloseButton type="button" data-dismiss="modal" aria-label="Close" onClick={hide}>{closeIcon}</CloseButton>
</Header>
<Content>{modalContent}</Content>
</StyledModal>
</Wrapper>
</FocusLock>
</Fragment>
);
return isShown ? createPortal(modal, document.body) : null;
};
export default Modal
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END