<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不使用第三方庫(kù)怎么實(shí)現(xiàn)【前端引導(dǎo)頁(yè)】功能?

          共 10802字,需瀏覽 22分鐘

           ·

          2022-11-09 02:31

          點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)

          回復(fù)加群,加入前端Q技術(shù)交流群

          前言

          隨著應(yīng)用功能越來(lái)越多,繁多而詳細(xì)的功能使用和說(shuō)明文檔,已經(jīng)不能滿足時(shí)代追求 快速 的需求,而 引導(dǎo)頁(yè)(或分步引導(dǎo)) 本質(zhì)就是 化繁為簡(jiǎn),將核心功能以更簡(jiǎn)單、簡(jiǎn)短、明了的文字指引用戶去使用對(duì)應(yīng)的功能,特別是 ToB 的項(xiàng)目,各種新功能需求迭代非???,免不了需要 引導(dǎo)頁(yè) 的功能來(lái)快速幫助用戶引導(dǎo)。

          下面我們通過(guò)兩個(gè)方面來(lái)圍繞著【前端引導(dǎo)頁(yè)】進(jìn)行展開(kāi):

          • 哪些第三方庫(kù)可以直接使用快速實(shí)現(xiàn)功能?
          • 如何自己實(shí)現(xiàn)前端引導(dǎo)頁(yè)的功能?

          第三方庫(kù)的選擇

          如果你不知道如何做技術(shù)選型,可以看看 山月大佬 的這一篇文章 在前端中,如何更好地做技術(shù)選型?[2],下面就簡(jiǎn)單列舉幾個(gè)相關(guān)的庫(kù)進(jìn)行簡(jiǎn)單介紹,具體需求具體分析選擇,其他和 API 使用、具體實(shí)現(xiàn)效果可以通過(guò)官方文檔或?qū)?yīng)的 README.md 進(jìn)行查看。

          vue-tour

          **`vue-tour`**[3] 是一個(gè)輕量級(jí)、簡(jiǎn)單且可自定義的 Tour 插件,配置也算比較簡(jiǎn)單清晰,但只適用于 Vue2 的項(xiàng)目,具體效果可以直接參考對(duì)應(yīng)的前面鏈接對(duì)應(yīng)的內(nèi)容。

          driver.js

          **`driver.js`**[4] 是一個(gè)強(qiáng)大而輕量級(jí)的普通 JavaScript 引擎,可在整個(gè)頁(yè)面上驅(qū)動(dòng)用戶的注意力,只有 4kb 左右的體積,并且沒(méi)有外部依賴,不僅高度可定制,還可以支持所有主流瀏覽器。

          shepherd.js

          **`shepherd.js`**[5] 包含的 API 眾多,大多場(chǎng)景都可以通過(guò)其對(duì)應(yīng)的配置得到,缺點(diǎn)就是整體的包體積較大,并且配置也比較復(fù)雜,配置復(fù)雜的內(nèi)容一般都需要進(jìn)行二次封裝,將可變和不可變的配置項(xiàng)進(jìn)行抽離,具體效果可見(jiàn)其 **官方文檔**[6]。

          intro.js

          **`intro.js`**[7] 是是一個(gè)開(kāi)源的 vanilla Javascript/CSS 庫(kù),用于添加分步介紹或提示,大小在 10kB左右,屬于輕量級(jí)的且無(wú)外部依賴,詳情可見(jiàn) **官方文檔**[8]

          實(shí)現(xiàn)引導(dǎo)頁(yè)功能

          引導(dǎo)頁(yè)核心功能其實(shí)就兩點(diǎn):

          • 一是 高亮部分
          • 二是 引導(dǎo)部分

          而這兩點(diǎn)其實(shí)真的不難實(shí)現(xiàn),無(wú)非就是 引導(dǎo)部分 跟著 高亮部分 移動(dòng),并且添加一些簡(jiǎn)單的動(dòng)畫或過(guò)渡效果即可,也分為 蒙層引導(dǎo)無(wú)蒙層引導(dǎo),這里介紹相對(duì)比較復(fù)雜的 蒙層引導(dǎo),下面就簡(jiǎn)單介紹兩種簡(jiǎn)單的實(shí)現(xiàn)方案。

          cloneNode + position + transition

          核心實(shí)現(xiàn):

          • 高亮部分 通過(guò) el.cloneNode(true) 復(fù)制對(duì)應(yīng)目標(biāo)元素節(jié)點(diǎn),并將克隆節(jié)點(diǎn)添加到蒙層上
            • 通過(guò) margin(或 tranlate、position 等)實(shí)現(xiàn)克隆節(jié)點(diǎn)的位置與目標(biāo)節(jié)點(diǎn)重合
          • 引導(dǎo)部分 通過(guò) position: fixed 實(shí)現(xiàn)定位效果,并通過(guò)動(dòng)態(tài)修改 left、top 屬性實(shí)現(xiàn)引導(dǎo)彈窗跟隨目標(biāo)移動(dòng)
          • 過(guò)渡動(dòng)畫 通過(guò) transition 實(shí)現(xiàn)位置的平滑移動(dòng)
          • 頁(yè)面 位置/內(nèi)容 發(fā)生變化時(shí)(如:resize、scroll 事件),需要重新計(jì)算位置信息

          缺點(diǎn):

          • 目標(biāo)節(jié)點(diǎn)需要被深度復(fù)制
          • 不能實(shí)現(xiàn)邊引導(dǎo)邊操作

          效果演示:

          核心代碼:

          // 核心配置參數(shù)
          const selectors = [
            {
              selector"#btn1",
              message"點(diǎn)此【新增】數(shù)據(jù)!",
            },
            {
              selector"#btn2",
              message"小心【刪除】數(shù)據(jù)!",
            },
            {
              selector"#btn3",
              message"可通過(guò)此按鈕【修改】數(shù)據(jù)!",
            },
            {
              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;
            }

            // 獲取目標(biāo)節(jié)點(diǎn)信息
            currNode =
              currNode || document.querySelector(props.selectors[index.value].selector);
            const { x, y, width, height } = currNode.getBoundingClientRect();

            // 克隆節(jié)點(diǎn)
            cloneNode = hasChange ? currNode.cloneNode(true) : cloneNode;
            cloneNode.id = currNode.id + "_clone";
            cloneNode.style = `
            margin-left: ${x}px;
            margin-top: ${y}px;
            `;

            // 指引相關(guān)
            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);
            }
          };

          // 頁(yè)面內(nèi)容發(fā)生變化時(shí),重新計(jì)算位置
          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-index999;
            left0;
            right0;
            top0;
            bottom0;
            background-colorrgba(0000.3);
          }
          .guide-box {
            width150px;
            min-height10px;
            border-radius5px;
            background-color#fff;
            position: absolute;
            transition0.5s;
            padding10px;
            text-align: center;
          }
          .btn {
            margin20px 5px 5px 5px;
          }
          </style>

          復(fù)制代碼

          z-index + position + transition

          核心實(shí)現(xiàn):

          • 高亮部分 通過(guò)控制 z-index 的值,讓目標(biāo)元素展示在蒙層之上
          • 引導(dǎo)部分 通過(guò) position: fixed 實(shí)現(xiàn)定位效果,并通過(guò)動(dòng)態(tài)修改 left、top 屬性實(shí)現(xiàn)引導(dǎo)彈窗跟隨目標(biāo)移動(dòng)
          • 過(guò)渡動(dòng)畫 通過(guò) transition 實(shí)現(xiàn)位置的平滑移動(dòng)
          • 頁(yè)面 位置/內(nèi)容 發(fā)生變化時(shí)(如:resize、scroll 事件),需要重新計(jì)算位置信息

          缺點(diǎn):

          • 當(dāng)目標(biāo)元素的父元素 position: fixed | absolute | sticky 時(shí),目標(biāo)元素的 z-index 無(wú)法超過(guò)蒙版層(可參考 shepherd.jssvg 解決方案)

          效果演示:

          核心代碼:

          <script setup>
          import { computed, onMounted, ref } from "vue";

          const props = defineProps({
            selectorsArray,
          });

          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;
            }

            // 修改上一個(gè)節(jié)點(diǎn)的 z-index
            if (preNode) preNode.style = `z-index: 0;`;

            // 獲取目標(biāo)節(jié)點(diǎn)信息
            const target =
              preNode = document.querySelector(props.selectors[index.value].selector);
            target.style = `
            position: relative; 
            z-index: 1000;
            `
          ;
            const { x, y, width, height } = target.getBoundingClientRect();

            // 指引相關(guān)
            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;
            `;
            }
          };

          // 頁(yè)面內(nèi)容發(fā)生變化時(shí),重新計(jì)算位置
          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>
          復(fù)制代碼

          【擴(kuò)展】SVG 如何完美解決 z-index 失效的問(wèn)題?

          這里以 **`shepherd.js`**[9] 來(lái)舉例說(shuō)明,先來(lái)看起官方文檔展示的 demo 效果:

          在上述展示的效果中進(jìn)行了一些驗(yàn)證:

          • 正常點(diǎn)擊 NEXT 進(jìn)入下一步指引,仔細(xì)觀察 SVG 相關(guān)數(shù)據(jù)發(fā)生了變化
          • 等到指引部分指向代碼塊的內(nèi)容區(qū)時(shí),復(fù)制了此時(shí) SVG 中和 path 相關(guān)的參數(shù)
          • 返回到第一步很明顯此時(shí)的高亮部分高度較小,將上一步復(fù)制的參數(shù)直接替換當(dāng)前 SVG 中和 path 相關(guān)的參數(shù),此時(shí)發(fā)現(xiàn)整體 SVG 高亮內(nèi)容寬高發(fā)生了變化

          核心結(jié)論:通過(guò) SVG 可編碼的特點(diǎn),利用 SVG 來(lái)實(shí)現(xiàn)蒙版效果,并且在繪制蒙版時(shí),預(yù)留出目標(biāo)元素的高亮區(qū)間(即 SVG 不需要繪制這一部分),這樣就解決了使用 z-index 可能會(huì)失效的問(wèn)題。

          最后

          以上就是一些簡(jiǎn)單實(shí)現(xiàn),但還有很多細(xì)節(jié)需要考慮,比如:邊引導(dǎo)邊操作的實(shí)現(xiàn)、定位原因?qū)е碌膱D層展示問(wèn)題等仍需要優(yōu)化。

          相信大部分人第一直覺(jué)是:直接使用第三方庫(kù)實(shí)現(xiàn)功能就好了呀,自己實(shí)現(xiàn)功能不全、也未必好用,屬實(shí)沒(méi)有必要。

          對(duì)于這一點(diǎn)其實(shí)在早前看到的一句話說(shuō)的挺好:了解底層實(shí)現(xiàn)原理比使用庫(kù)本身更有意義,當(dāng)然每個(gè)人的想法不同,不過(guò)如果你想開(kāi)始了解原理又不能立馬挑戰(zhàn)一些高深的內(nèi)容,為什么不先從自己感興趣的又不是那么復(fù)雜的功能開(kāi)始呢?

          2867E693.jpg


          關(guān)于本

          作者:熊的貓

          https://juejin.cn/post/7142633594882621454


          往期推薦


          比Webpack快700倍的Turbopack,到底快在哪?
          重磅!哈啰 Quark Design 正式開(kāi)源,下一代跨技術(shù)棧前端組件庫(kù)
          【復(fù)盤】我在 Vue3 開(kāi)發(fā)中踩的坑

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧

          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人三级在线观看 | 国产美女极度色诱视频WWW | 99久久精品毛片视频 | 天天操天天添 | 亚洲日韩Av无码中文字幕美国 |