我报名参与金石方案1期挑战——瓜分10万奖池,这是我的第2篇文章,点击检查活动详情

前言

跟着运用功用越来越多,繁多而详细的功用运用和说明文档,现已不能满意年代寻求 快速 的需求,而 引导页(或分步引导) 本质便是 化繁为简,将中心功用以更简略、简略、明晰的文字指引用户去运用对应的功用,特别是 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(或 tranlateposition 等)完成克隆节点的方位与方针节点重合
  • 引导部分 经过 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.jssvg 解决方案)

作用演示:

不运用第三方库怎样完成【前端引导页】功用?

中心代码:

<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 导入和导出功用
  • 前端性能优化到底该怎样做(上)— 开门见山
  • 前端性能优化到底该怎样做(下)— 直捣黄龙
  • 你知道前端水印功用是怎样完成的吗?