对于新上线的页面,往往会加上一个新手引导功能,告诉用户应该如何使用,如下图所示:
那如何实现这个新手引导的功能呢?我的需求只有两个:
- 支持 React 技术栈
- 能够自定义布局和样式
经过一番调研,发现一些 star 比较多的库,其中一类是通用性技术,但有各种框架(包括 React)的 wrapper,例如:
还有一种是纯 React 技术栈的解决方案,例如:
接下来分别对这些库进行踩坑,并在最后给出自己比较推荐的新手引导库。
intro.js-react
示例代码和 demo 效果可以参考:codesandbox.io/embed/o2A4g…
使用方法比较简单,只需要设置一个 steps 数组,明确每一步的引导分别挂在哪个元素上、气泡展现的方向和气泡内容即可:
const steps = [
{
element: '.contact-form',
position: 'bottom',
intro: (
<>
<div className="guide-cover">
<img src="xxx.png" alt="引导图片" />
</div>
<div className="guide-title">引导标题</div>
<div className="guide-content">引导内容</div>
</>
),
},
]
虽然引导内容可以自定义,但是引导的布局不好调整,官方只提供了几个可用的选项,例如:
const options = {
showBullets: false,
hidePrev: true,
skipLabel: '跳过',
prevLabel: '上一步',
nextLabel: '下一步',
doneLabel: '完成',
showStepNumbers: true,
stepNumbersOfLabel: '/',
}
这里面最令人无法接受的是不能自定义上一步和下一步按钮,只能改一些文案,如果想替换原始的 DOM,并绑定自己的事件时,似乎无能为力。无奈只能硬着头皮看了一下源码,都是原生 JS 写的,直接操作 DOM 创建各种元素:
所以确实不太好自定义布局,这是这个库不好的地方,但好的地方就是它没有任何其他依赖,一个很纯粹的包,集成进去的代码量不大。但是有个非常不好的地方在于它的 License 不是 MIT 的,而是 AGPLv3 协议,好家伙,这个协议的杀伤力非常大:
AGPL v3协议:除非获得商业授权,否则无论以何种方式修改或者使用代码,都需要开源。
如果你是商业产品,强烈建议不要用 intro.js,否则会坑了公司,综合推荐指数:⭐️⭐️
react-shepherd
这个库也是封装的 stepherd ,我看了一会就弃坑了,因为配置非常复杂(不知道你看了下面的部分配置项之后何种感受,我反正晕了):
const steps = [
{
id: 'intro',
attachTo: { element: '.first-element', on: 'bottom' },
beforeShowPromise: function () {
return new Promise(function (resolve) {
setTimeout(function () {
window.scrollTo(0, 0);
resolve();
}, 500);
});
},
buttons: [
{
classes: 'shepherd-button-secondary',
text: 'Exit',
type: 'cancel'
},
{
classes: 'shepherd-button-primary',
text: 'Back',
type: 'back'
},
{
classes: 'shepherd-button-primary',
text: 'Next',
type: 'next'
}
],
classes: 'custom-class-name-1 custom-class-name-2',
highlightClass: 'highlight',
scrollTo: false,
cancelIcon: {
enabled: true,
},
title: 'Welcome to React-Shepherd!',
text: ['React-Shepherd is a JavaScript library for guiding users through your React app.'],
when: {
show: () => {
console.log('show step');
},
hide: () => {
console.log('hide step');
}
}
},
// ...
];
配置复杂一点我也就忍了,但是跑起来的效果那叫一个丑啊:
老土的标题区,只能设置字符串数组的内容区,还有那 bootstrap 风格的巨大按钮区,让人怎么看都不舒服。鉴于配置复杂时间宝贵,建议大家不要选择这个库,尽早弃坑,综合推荐指数:⭐️
reactour
这个库是专门为 React 框架设计的,定制化的能力比较强,可以参考 demo、sandbox 或文档。基础使用方法和 intro.js 比较类似,也是先定义 steps:
const steps = [
{
selector: '.first',
position: 'bottom' as any,
content: "字符串或 jsx 都行"
}
]
使用的时候,需要套一个 TourProvider 在外面:
export default function ({ children }) {
return <TourProvider {...props}>{children}</TourProvider>
}
属性也挺多的,需要耐心看一下文档,例如:
const props = {
steps,
defaultOpen: true,
showBadge: false,
showNavigation: true,
showDots: false,
className: 'user-guide',
styles: {
maskArea: (base) => ({ ...base, rx: 10 }),
},
padding: { mask: 0 },
onClickMask: () => {},
ContentComponent,
}
有一点需要注意,它的默认遮罩的高亮区域是用 svg 做的,不能用 borderRadius 控制圆角,要用 rx 来控制才行:
配置项中的 ContentComponent 用于自定义气泡内容,是个函数组件,非常灵活,可以重写全部的布局,例如:
function ContentComponent(props) {
const { currentStep, steps, setIsOpen, setCurrentStep } = props
return (
<div className="user-guide-content">
// 这里可以让用户自定义气泡布局
</div>
)
}
这是比上面两个库好很多的地方:UI 的自定义能力!不过比较坑的一点是:引导气泡默认没有箭头效果,需要自己写,好在写个箭头并不复杂,简单的代码如下:
.user-guide-content {
padding: 15px;
min-width: 300px;
&::after {
content: '';
width: 0;
height: 0;
position: absolute;
}
&.bottom-arrow::after {
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 12px solid white;
left: 22px;
top: -10px;
}
&.right-arrow::after {
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 12px solid white;
top: 22px;
left: -10px;
}
&.left-arrow::after {
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 12px solid white;
top: 22px;
right: -10px;
}
&.top-arrow::after {
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 12px solid white;
left: 22px;
bottom: -10px;
}
}
不过上面的简单效果不适用于页面缩放的自适应场景,在 steps 里面会设置气泡默认的方向,但是当缩放的时候,由于空间不足的原因会改变气泡的相对位置,这个时候箭头的位置也要做相应的调整才行,否则会出现下面的效果:
其实解决起来也很简单,可以参考我给作者提的这个 issue ,利用 css variable 来动态设置宿主的样式,然后在 ::after伪类里面引用变量。总而言之,这个库还是非常不错的,持续维护,作者响应 issue 很积极,综合推荐指数:⭐️⭐️⭐️⭐️,最后附上完整的示例代码:
import './Reactour.scss'
import { TourProvider } from '@reactour/tour'
import Layout from './Layout'
const steps = [
{
selector: '.contact-form',
position: 'right' as any,
content: (
<>
<div className="guide-cover">
<img src="/write.jpeg" />
</div>
<div className="guide-title">简约而不失优雅的表单</div>
<div className="guide-content">请在左侧表单区域填写您的姓名、邮箱、标题和内容,输入完成后点击提交按钮即可。</div>
</>
),
},
{
selector: '.map',
position: 'left',
content: (
<>
<div className="guide-title">谷歌地图快速定位</div>
<div className="guide-content">双击鼠标可快速放大地图,您可以通过拖动地图来选择您的位置。</div>
</>
),
},
{
selector: '.street',
position: 'left',
content: (
<>
<div className="guide-title">联系我们</div>
<div className="guide-content">详细的街道、邮箱和电话</div>
</>
),
},
]
const stepArrowDirections = ['right', 'left', 'left']
function ContentComponent(props) {
const { currentStep, steps, setIsOpen, setCurrentStep } = props
const isLastStep = currentStep === steps.length - 1
const content = steps[currentStep].content
const close = () => {
setIsOpen(false)
localStorage.setItem('skip_user_guide', 'true')
}
return (
<div className={`user-guide-content ${stepArrowDirections[currentStep]}-arrow`}>
{typeof content === 'function' ? content({ ...props }) : content}
<div className="guide-navigation">
<div className="guide-prev" onClick={() => close()}>
{`${'跳过'} (${currentStep + 1}/${steps.length})`}
</div>
<div
className="guide-next"
onClick={() => {
if (isLastStep) {
close()
} else {
setCurrentStep((s) => s + 1)
}
}}
>
{isLastStep ? '完成' : '下一步'}
</div>
</div>
</div>
)
}
const root = document.getElementById('root')
const props = {
steps,
defaultOpen: !localStorage.getItem('skip_user_guide'),
afterOpen: () => (root!.style.overflow = 'hidden'),
beforeClose: () => (root!.style.overflow = 'auto'),
showBadge: false,
showNavigation: true,
showDots: false,
ContentComponent,
className: 'user-guide',
styles: {
maskArea: (base) => ({ ...base, rx: 5 }),
},
onClickMask: () => {},
}
export default function () {
return (
<TourProvider {...props}>
<Layout />
</TourProvider>
)
}
react-joyride
这个库也有非常强的 UI 定制能力,配置项也不复杂,跟 reactour 的思想非常接近,基本上可以满足所有项目的需求了,而且默认自带气泡箭头,如下图所示:
综合推荐综合指数:⭐️⭐️⭐️⭐️⭐️,话不多说,直接附上完整示例代码:
import './Joyride.scss'
import Joyride from 'react-joyride'
import Layout from './Layout'
const locale = {
back: '上一步',
next: '下一步',
skip: '跳过',
close: '下一步',
last: '完成',
}
const steps = [
{
target: '.contact-form',
placement: 'right' as any,
disableBeacon: true,
hideCloseButton: true,
locale,
spotlightClicks: true,
content: (
<>
<div className="guide-cover">
<img src="/write.jpeg" />
</div>
<div className="guide-title">简约而不失优雅的表单</div>
<div className="guide-content">请在左侧表单区域填写您的姓名、邮箱、标题和内容,输入完成后点击提交按钮即可。</div>
</>
),
},
{
target: '.map',
placement: 'left' as any,
disableBeacon: true,
hideCloseButton: true,
locale,
content: (
<>
<div className="guide-title">谷歌地图快速定位</div>
<div className="guide-content">双击鼠标可快速放大地图,您可以通过拖动地图来选择您的位置。</div>
</>
),
},
{
target: '.street',
placement: 'top' as any,
disableBeacon: true,
hideCloseButton: true,
locale,
content: (
<>
<div className="guide-title">联系我们</div>
<div className="guide-content">详细的街道、邮箱和电话</div>
</>
),
},
]
const Tooltip = ({ continuous, index, step, backProps, closeProps, primaryProps, tooltipProps }) => (
<div className="tooltip-body" {...tooltipProps}>
{step.title && <div className="tooltip-title">{step.title}</div>}
<div className="tooltip-content">{step.content}</div>
<div className="tooltip-footer guide-navigation">
<div className="guide-prev" {...backProps}>
{index > 0 ? '上一步' : ''}
</div>
{index + 1 === steps.length ? (
<div className="guide-next" {...closeProps}>完成</div>
) : (
<div className="guide-next" {...primaryProps}>
下一步({`${index + 1}/${steps.length}`})
</div>
)}
</div>
</div>
)
export default function () {
return (
<div className="app">
<Joyride showProgress continuous={true} tooltipComponent={Tooltip} steps={steps} />
<Layout />
</div>
)
}
结论
React 新手引导库开箱评测的结果如下(仅代表个人意见,仅供参考,如有冒犯,还望海涵):
| 框架名称 | License | 复杂性 | 扩展性 | 推荐指数 |
|---|---|---|---|---|
| intro.js 的封装 ( intro.js-react ) | 原库:AGPLv3,封装库:MIT | 简单 | 不易扩展 | ⭐️⭐️ |
| stepherd 的封装 ( react-shepherd ) | 原库:MIT,封装库:MIT | 复杂 | 不易扩展 | ⭐️ |
| reactour | MIT | 中等 | 易扩展 | ⭐️⭐️⭐️⭐️ |
| react-joyride | MIT | 简单 | 易扩展 | ⭐️⭐️⭐️⭐️⭐ |