共计 5833 个字符,预计需要花费 15 分钟才能阅读完成。
前言
随着利用性能越来越多,繁多而具体的性能应用和阐明文档,曾经不能满足时代谋求 疾速 的需要,而 疏导页(或分步疏导) 实质就是 化繁为简 ,将外围性能以更简略、简短、明了的文字指引用户去应用对应的性能,特地是 ToB
的我的项目,各种新性能需要迭代十分快,免不了须要 疏导页 的性能来疾速帮忙用户疏导。
上面咱们通过两个方面来围绕着【前端疏导页】进行开展:
- 哪些第三方库能够间接应用疾速实现性能?
- 如何本人实现前端疏导页的性能?
第三方库的抉择
如果你不晓得如何做技术选型,能够看看 山月大佬 的这一篇文章 在前端中,如何更好地做技术选型?,上面就简略列举几个相干的库进行简略介绍,具体需要具体分析抉择,其余和 API
应用、具体实现成果能够通过官网文档或对应的 README.md
进行查看。
vue-tour
vue-tour
是一个轻量级、简略且可自定义的 Tour
插件,配置也算比较简单清晰,但只实用于 Vue2
的我的项目,具体成果能够间接参考对应的后面链接对应的内容。
driver.js
driver.js
是一个弱小而轻量级的一般 JavaScript
引擎,可在整个页面上驱动用户的注意力,只有 4kb
左右的体积,并且没有内部依赖,不仅高度可定制,还能够反对所有支流浏览器。
shepherd.js
shepherd.js
蕴含的 API
泛滥,大多场景都能够通过其对应的配置失去,毛病就是整体的包体积较大,并且配置也比较复杂,配置简单的内容个别都须要进行二次封装,将可变和不可变的配置项进行抽离,具体成果可见其 官网文档。
intro.js
intro.js
是是一个开源的 vanilla Javascript/CSS
库,用于增加分步介绍或提醒,大小在 10kB
左右,属于轻量级的且无内部依赖,详情可见 官网文档。
实现疏导页性能
疏导页外围性能其实就两点:
- 一是 高亮局部
- 二是 疏导局部
而这两点其实真的不难实现,无非就是 疏导局部 跟着 高亮局部 挪动,并且增加一些简略的动画或过渡成果即可,也分为 蒙层疏导 和 无蒙层疏导 ,这里介绍绝对比较复杂的 蒙层疏导,上面就简略介绍两种简略的实现计划。
cloneNode + position + transition
外围实现:
-
高亮局部 通过
el.cloneNode(true)
复制对应指标元素节点,并将克隆节点增加到蒙层上- 通过
margin
(或tranlate
、position
等)实现克隆节点的地位与指标节点重合
- 通过
- 疏导局部 通过
position: fixed
实现定位成果,并通过动静批改left、top
属性实现疏导弹窗追随指标挪动 - 过渡动画 通过
transition
实现地位的平滑挪动 - 页面 地位 / 内容 发生变化时(如:
resize、scroll
事件),须要从新计算地位信息
毛病:
- 指标节点须要被深度复制
- 不能实现边疏导边操作
成果演示:
外围代码:
// 外围配置参数
const selectors = [
{
selector: "#btn1",
message: "点此【新增】数据!",
},
{
selector: "#btn2",
message: "小心【删除】数据!",
},
{
selector: "#btn3",
message: "可通过此按钮【批改】数据!",
},
{
selector: "#btn4",
message: "一键【实现】所有操作!",
},
];
// Guide.vue
<script setup>
import {computed, onMounted, ref} from "vue";
const props = defineProps({selectors: Array,});
const guideModalRef = ref(null);
const guideBoxRef = ref(null);
const index = ref(0);
const show = ref(true);
let cloneNode = null;
let currNode = null;
let message = computed(() => {return props.selectors[index.value]?.message;
});
const genGuide = (hasChange = true) => {
// 前置操作
cloneNode && guideModalRef.value?.removeChild(cloneNode);
// 所有指引结束
if (index.value > props.selectors.length - 1) {
show.value = false;
return;
}
// 获取指标节点信息
currNode =
currNode || document.querySelector(props.selectors[index.value].selector);
const {x, y, width, height} = currNode.getBoundingClientRect();
// 克隆节点
cloneNode = hasChange ? currNode.cloneNode(true) : cloneNode;
cloneNode.id = currNode.id + "_clone";
cloneNode.style = `
margin-left: ${x}px;
margin-top: ${y}px;
`;
// 指引相干
if (guideBoxRef.value) {
const halfClientHeight = guideBoxRef.value.clientHeight / 2;
guideBoxRef.value.style = `
left:${x + width + 10}px;
top:${y <= halfClientHeight ? y : y - halfClientHeight + height / 2}px;
`;
guideModalRef.value?.appendChild(cloneNode);
}
};
// 页面内容发生变化时,从新计算地位
window.addEventListener("resize", () => genGuide(false));
window.addEventListener("scroll", () => genGuide(false));
// 上一步 / 下一步
const changeStep = (isPre) => {
isPre ? index.value-- : index.value++;
currNode = null;
genGuide();};
onMounted(() => {genGuide();
});
</script>
<template>
<teleport to="body">
<div v-if="show" ref="guideModalRef" class="guide-modal">
<div ref="guideBoxRef" class="guide-box">
<div>{{message}}</div>
<button class="btn" :disabled="index === 0" @click="changeStep(true)">
上一步
</button>
<button class="btn" @click="changeStep(false)"> 下一步 </button>
</div>
</div>
</teleport>
</template>
<style scoped>
.guide-modal {
position: fixed;
z-index: 999;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
}
.guide-box {
width: 150px;
min-height: 10px;
border-radius: 5px;
background-color: #fff;
position: absolute;
transition: 0.5s;
padding: 10px;
text-align: center;
}
.btn {margin: 20px 5px 5px 5px;}
</style>
z-index + position + transition
外围实现:
- 高亮局部 通过管制
z-index
的值,让指标元素展现在蒙层之上 - 疏导局部 通过
position: fixed
实现定位成果,并通过动静批改left、top
属性实现疏导弹窗追随指标挪动 - 过渡动画 通过
transition
实现地位的平滑挪动 - 页面 地位 / 内容 发生变化时(如:
resize、scroll
事件),须要从新计算地位信息
毛病:
- 当指标元素的父元素
position: fixed | absolute | sticky
时,指标元素的z-index
无奈超过蒙版层(可参考shepherd.js
的svg
解决方案)
成果演示:
外围代码:
<script setup>
import {computed, onMounted, ref} from "vue";
const props = defineProps({selectors: Array,});
const guideModalRef = ref(null);
const guideBoxRef = ref(null);
const index = ref(0);
const show = ref(true);
let preNode = null;
let message = computed(() => {return props.selectors[index.value]?.message;
});
const genGuide = (hasChange = true) => {
// 所有指引结束
if (index.value > props.selectors.length - 1) {
show.value = false;
return;
}
// 批改上一个节点的 z-index
if (preNode) preNode.style = `z-index: 0;`;
// 获取指标节点信息
const target =
preNode = document.querySelector(props.selectors[index.value].selector);
target.style = `
position: relative;
z-index: 1000;
`;
const {x, y, width, height} = target.getBoundingClientRect();
// 指引相干
if (guideBoxRef.value) {
const halfClientHeight = guideBoxRef.value.clientHeight / 2;
guideBoxRef.value.style = `
left:${x + width + 10}px;
top:${y <= halfClientHeight ? y : y - halfClientHeight + height / 2}px;
`;
}
};
// 页面内容发生变化时,从新计算地位
window.addEventListener("resize", () => genGuide(false));
window.addEventListener("scroll", () => genGuide(false));
const changeStep = (isPre) => {
isPre ? index.value-- : index.value++;
genGuide();};
onMounted(() => {genGuide();
});
</script>
<template>
<teleport to="body">
<div v-if="show" ref="guideModalRef" class="guide-modal">
<div ref="guideBoxRef" class="guide-box">
<div>{{message}}</div>
<button class="btn" :disabled="index === 0" @click="changeStep(true)">
上一步
</button>
<button class="btn" @click="changeStep(false)"> 下一步 </button>
</div>
</div>
</teleport>
</template>
<style scoped>
.guide-modal {
position: fixed;
z-index: 999;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
}
.guide-box {
width: 150px;
min-height: 10px;
border-radius: 5px;
background-color: #fff;
position: absolute;
transition: 0.5s;
padding: 10px;
text-align: center;
}
.btn {margin: 20px 5px 5px 5px;}
</style>
【扩大】SVG
如何完满解决 z-index
生效的问题?
这里以 shepherd.js
来举例说明,先来看起官网文档展现的 demo
成果:
在上述展现的成果中进行了一些验证:
- 失常点击
NEXT
进入下一步指引,仔细观察SVG
相干数据产生了变动 - 等到指引局部指向代码块的内容区时,复制了此时
SVG
中和path
相干的参数 - 返回到第一步很显著此时的高亮局部高度较小,将上一步复制的参数间接替换以后
SVG
中和path
相干的参数,此时发现整体SVG
高亮内容宽高产生了变动
外围论断:通过 SVG
可编码的特点,利用 SVG
来实现蒙版成果,并且在绘制蒙版时,预留出指标元素的高亮区间(即 SVG
不须要绘制这一部分),这样就解决了应用 z-index
可能会生效的问题。
最初
以上就是一些简略实现,但还有很多细节须要思考,比方:边疏导边操作的实现、定位起因导致的图层展现问题等仍须要优化。
置信大部分人第一直觉是:间接应用第三方库实现性能就好了呀,本人实现性能不全、也未必好用,属实没有必要。
对于这一点其实在早前看到的一句话说的挺好:理解底层实现原理比应用库自身更有意义,当然每个人的想法不同,不过如果你想开始理解原理又不能立马挑战一些浅近的内容,为什么不先从本人感兴趣的又不是那么简单的性能开始呢?
往期文章举荐
- 2022 你还不会微前端吗 (下) — 揭秘微前端外围原理
- 2022 你还不会微前端吗 (上) — 从巨石利用到微利用
- 据说你很理解 Vue3 响应式?
- 给我实现一个前端的 Excel 导入和导出性能
- 前端性能优化到底该怎么做(上)— 单刀直入
- 前端性能优化到底该怎么做(下)— 直捣黄龙
- 你晓得前端水印性能是怎么实现的吗?