引导页这个组件,相信大家或多或少都见过吧,就像下面这样:
想要实现这个功能,如果借助第三方库来做的话,其实很简单,比如driver.js
就可以做到。就像下面这样:
javascript
import React, { useEffect } from 'react';
import { driver } from 'driver.js';
function App(){
useEffect(
() => {
let driverObj = driver({
showProgress: true,
steps: [
{
element: '.one',
popover: {
title: 'Title',
description: 'Description'
}
},
{
element: '.two',
popover: {
title: 'Title',
description: 'Description'
}
}
]
});
driverObj.drive();
}
);
return <div>
<div className='item one' onClick={click1}>第一个元素</div>
<div className='item two'>第二个元素</div>
<div className='item three'>第三个元素</div>
</div>
}
export default App;
效果如下:
那接下来,我们就一起看看如何从0实现它。
一、这个组件真的是公共组件吗?
首先说一下这个组件的核心点,如下:
如何实现元素高亮效果?
那一定是衬托
。就像上图一样,第一个元素
为什么能看出来是高亮的
,是因为它周围都是黑色的
,颜色不同,效果自然不一样。
1.1、如何实现高亮效果?
2种方式,一种方式是"层级",一种方式是svg。
1.2、层级
层级嘛,很简单,我不写代码,相信大家也一定会。
代码如下:
html
<style>
.one {
margin-left: 200px;
margin-top: 100px;
width: 200px;
height: 200px;
border-radius: 50%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
background: cornflowerblue;
z-index: 2000;
position: relative;
}
.shadow-box {
position: fixed;
width: 100%;
height: 100%;
box-sizing: border-box;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
pointer-events: none;
background-color: #111;
opacity: 0.7;
}
</style>
<div class="one" onclick="clickElement()">第一个元素</div>
<div class="shadow-box"></div>
效果如下:
1.3、svg
这种方式也是driver.js
使用的。代码如下:
html
<style>
svg {
width: 100%;
height: 100%;
box-sizing: border-box;
position: fixed;
left: 0px;
bottom: 0px;
top: 0px;
right: 0px;
z-index: 1000;
}
</style>
<svg>
<path
d="
M792,0
L0,0
L0,857
L792,857
L792,0
Z
M0,0
h210
v110
h-210
v-110
Z
"
></path>
</svg>
效果如下:
svg这个标签,你想要画什么图案,它都有对应的标签能够满足你。比如<rect>用来画矩形
,<circle>用来画圆形
、<path>用来画路径
。
path标签里,d属性用来执行具体的绘画命令。它有很多个属性或者命令,这里不做赘述,想要弄清楚的可以自己去查阅MDN。
有一点我需要声明,我在MDN上没有查到这种写法,就是一个d属性里跟着多个Z。而且这种写法会触发多种怪异的行为,大家可以试一下(一个标签里,执行多个封闭的命令),它的行为并不总能符合预期,只是对于"引导页高亮"的效果来说,它能满足,因为这种效果是规则的,就是画矩形嘛。
1.4、它能写成公共组件吗?
如果是我,我不会将它封装成公共组件。因为我不具备对它兜底的能力。
首先第一点,层级这种写法我是一定不会采纳的,因为这种写法对用户的代码具有侵略性,随意修改用户代码的层级,这听起来就不靠谱,做起来更不靠谱。
第二点,svg的这种写法,第二次绘制的时候,为什么背景是透明?第二次绘制的时候,封闭路径的背景颜色可以修改吗?我在MDN上找不到答案。同时,为什么我在后面追加了多种不规则形状的路径,它的表现不符合预期,我在MDN上也没找到对应的答案。
基于以上2点,这个功能,我肯定是能做,但我一定不会将它封装成公共组件。
二、手撸代码
我们这里依旧是低开低走
,只实现最简单的情况。
以2个元素之间的联动为例。
2.1、初始化
html
<div class="item one">one</div>
<div class="item two">two</div>
<button onclick="clickStartTour()">开始引导</button>
样式代码如下:
css
.item {
width: 100px;
height: 100px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
margin-top: 100px;
margin-left: 100px;
border-radius: 50%;
}
.one {
background-color: cornflowerblue;
}
.two {
background-color: orange;
margin-left: 150px;
}
现在的效果如下:
现在我们想着是点击"开始引导"按钮,然后就出现"one"元素高亮效果。
2.2、实现元素高亮
按钮点击事件如下:
javascript
// 点击"开始引导"按钮触发
function clickStartTour(){
let svgTag = document.createElementNS("http://www.w3.org/2000/svg", "svg");;
let pathTag = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgTag.appendChild(pathTag);
document.querySelector('body').appendChild(svgTag);
pathTag.setAttribute(
'd',
`
M0 0
L0 ${window.innerHeight}
L${window.innerWidth} ${window.innerHeight}
L${window.innerWidth} 0
Z
`
);
}
我们还需要给svg一个固定定位的样式,这样能够保证它是一个遮罩层。
css
svg {
width: 100%;
height: 100%;
box-sizing: border-box;
position: fixed;
z-index: 500;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
path {
opacity: 0.7;
}
现在当我们点击一下,就会有下面的这个效果:
接下来我们来获取到 one元素
的高亮区域块。
javascript
function highLightElement(element){
// 高亮区域块左上方的坐标
let highLightAreaPointX = element.offsetLeft - 10;
let hightLightAreaPointY = element.offsetTop - 10;
// 获取到元素的width与height,我们就可以获取到高亮矩形区域的所有坐标
let elementWidth = element.clientWidth + 20;
let elementHeight = element.clientHeight + 20;
let pathTag = document.querySelector('path');
pathTag.setAttribute(
'd',
`
M0 0
L0 ${window.innerHeight}
L${window.innerWidth} ${window.innerHeight}
L${window.innerWidth} 0
Z
M${highLightAreaPointX} ${hightLightAreaPointY}
L${highLightAreaPointX + elementWidth} ${hightLightAreaPointY}
L${highLightAreaPointX + elementWidth} ${hightLightAreaPointY + elementHeight}
L${highLightAreaPointX} ${hightLightAreaPointY + elementHeight}
Z
`
)
}
然后,在clickStartTour
函数的末尾,加上下面这句代码:
javascript
highLightElement(document.querySelector('.one'));
我们便可以得到这样的效果:
接下来就是在高亮区域旁边加上类似tooltip的提示,用于触发下一个块的高亮。
2.3、tooltip提示
这个tooltip轻提示,它也是一种公共的基础组件。目前我的UI专栏里汇聚了16种组件,虽然没有tooltip组件的身影,但是以后肯定会实现,而且我相信这个专���也一定会是全网组件实现里面,组件种类最多,讲解最通俗易懂的专栏。
好啦,成功跑题了,我们回到tooltip的这个实现里。我们还是噢,不去实现特别细的点,因为细的点往往是由自身业务产生的,我们这里能够实现最简单的demo即可。
javascript
function createTooltip(left, top){
// tooltip组件的最外层盒子
let divTag = document.createElement('div');
divTag.classList.add('tooltip-content');
// 头部
let headerTag = document.createElement('div');
let text = document.createTextNode('这是标题说明');
// 尾部
let footerTag = document.createElement('div');
footerTag.classList.add('footer');
// 按钮
let buttonTag = document.createElement('button');
let buttonName = document.createTextNode('下一步');
// 尾部元素添加按钮
footerTag.appendChild(buttonTag);
buttonTag.appendChild(buttonName);
headerTag.append(text);
headerTag.classList.add('header');
// tooltip最外层元素添加header
divTag.appendChild(headerTag);
// tooltip最外层元素添加footer
divTag.appendChild(footerTag);
// 设置tooltip组件的显示位置
divTag.style.top = `${top}px`;
divTag.style.left = `${left}px`;
document.querySelector('body').appendChild(divTag);
}
我们还需要给这个tooltip组件添加一些基础的css样式:
css
.tooltip-content {
width: 200px;
height: 100px;
background: #fff;
position: absolute;
z-index: 600;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
}
.tooltip-content::after {
content: '';
display: block;
position: absolute;
left: -10px;
top: 40%;
transform: rotate(-135deg);
width: 10px;
height: 10px;
border-top: 3px solid #EFEFEF;
border-right: 3px solid #EFEFEF;
}
.header {
width: 100%;
border-bottom: 1px solid cornflowerblue;
}
.footer {
width: 100%;
border-top: 1px solid cornflowerblue;
display: flex;
justify-content: flex-end;
box-sizing: border-box;
padding-right: 10px;
}
那这个tooltip提示产生的时机在哪里呢?是不是应该跟高亮区域一块显示的呀,因此我们还需要在highLightElement
方法里添加createTooltip
方法的调用。代码如下:
javascript
function highLightElement(element){
// 高亮区域块左上方的坐标
let highLightAreaPointX = element.offsetLeft - 10;
let hightLightAreaPointY = element.offsetTop - 10;
// 获取到元素的width与height,我们就可以获取到高亮矩形区域的所有坐标
let elementWidth = element.clientWidth + 20;
let elementHeight = element.clientHeight + 20;
let pathTag = document.querySelector('path');
pathTag.setAttribute(
'd',
`
M0 0
L0 ${window.innerHeight}
L${window.innerWidth} ${window.innerHeight}
L${window.innerWidth} 0
Z
M${highLightAreaPointX} ${hightLightAreaPointY}
L${highLightAreaPointX + elementWidth} ${hightLightAreaPointY}
L${highLightAreaPointX + elementWidth} ${hightLightAreaPointY + elementHeight}
L${highLightAreaPointX} ${hightLightAreaPointY + elementHeight}
Z
`
)
let tooltipTop = hightLightAreaPointY;
let tooltipLeft = highLightAreaPointX + elementWidth + 30;
createTooltip(tooltipLeft, tooltipTop);
}
现在当我们再次点击"开始引导"按钮的时候,就会出现下面这个效果:
很好哈,我们已经快成功了,接下来就是2个元素之间的高亮联动。
2.4、高亮元素联动
这个的触发时机没得说,就是点击"下一步"触发的。因此我们来补全下这块的代码。
按钮(下一步)
这个元素是在创建tooltip组件的时候产生的,所以我们需要修改下createTooltip
方法。
javascript
// 声明一个全局变量,用于标识当前高亮元素是否是最后一个
let isFinal = false;
// 给button添加click事件
function createTooltip(left, top){
// 其余代码不变 --------
// 给button添加click事件
buttonTag.onclick = function (){
if (isFinal){
// 因为是2个元素之间的联动,所以two元素高亮之后,再次点击"下一步"时,应该remove svg
nextHightLightElement(null);
if (document.querySelector('.tooltip-content')){
document.querySelector('body').removeChild(document.querySelector('.tooltip-content'));
}
return
}
isFinal = true;
// 注意这里,进行下个高亮元素的显示
nextHightLightElement(document.querySelector('.two'));
}
// 其余代码不变 --------
}
接下来我们就来看看nextHightLightElement
函数的实现。
javascript
function nextHightLightElement(element){
if (!element){
removeSvg();
return
}
highLightElement(element);
}
最后,我们修改下highLightElement
函数的实现,只需在最后一步加上如下代码:
javascript
let nextElement = document.querySelector('.two');
createTooltip(tooltipLeft, tooltipTop, nextElement);
至此,一个简单的引导页组件就实现了。效果如下:
三、最后
好啦,本期引导页组件的实现思路到这里就结束啦,在这个过程中,我也提了一些有关path标签绘制路径的一些疑惑,如果有大神知道这些问题的答案,欢迎评论区里指点一下,我们下期再见,拜拜~~