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

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

          共 7686字,需瀏覽 16分鐘

           ·

          2022-12-30 09:01

          前言

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

          下面我們通過兩個方面來圍繞著【前端引導(dǎo)頁】進行展開:

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

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

          vue-tour

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

          49163046d07eb03b7af2a0bed4a2b4df.webp

          driver.js

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

          951eec484c76963d101bc34868831c56.webp

          shepherd.js

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

          007f446c984c76e340ccfe76610bcad7.webp

          intro.js

          **`intro.js`**[7] 是是一個開源的 vanilla Javascript/CSS 庫,用于添加分步介紹或提示,大小在 10kB左右,屬于輕量級的且無外部依賴,詳情可見 **官方文檔**[8]。

          5a46cf2695f0f2f50102d79a4f2ad2a1.webp 實現(xiàn)引導(dǎo)頁功能

          引導(dǎo)頁核心功能其實就兩點:

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

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

          cloneNode + position + transition

          核心實現(xiàn):

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

          缺點:

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

          效果演示:

          c3c3848349d54d62e3555d0e23a89e20.webp

          核心代碼:

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

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

          ??//?克隆節(jié)點
          ??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);
          ??}
          };

          //?頁面內(nèi)容發(fā)生變化時,重新計算位置
          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>

          復(fù)制代碼

          z-index + position + transition

          核心實現(xiàn):

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

          缺點:

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

          效果演示:

          ea55a6976f4c119fd969447353ba4bb8.webp

          核心代碼:

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

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

          ??//?獲取目標節(jié)點信息
          ??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;
          ??`;
          ??}
          };

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

          【擴展】SVG 如何完美解決 z-index 失效的問題?

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

          928dd184765be5323169acf31c4a3cd0.webp

          在上述展示的效果中進行了一些驗證:

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

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

          最后

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

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

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

          c6a1edab63ae5716a45853ad089220ea.webp2867E693.jpg

          作者:熊的貓

          https://juejin.cn/post/7142633594882621454

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  日本护士XXXX1819 | 色综合久久88色综合天天看泰 | 中国熟妇XXX | 日韩中文字幕在线播放 | 亚洲黄色电影大全 |