OpenTiny Vue 組件庫適配微前端可能遇到的4個問題

本文由體驗技術團隊 TinyVue 項目成員岑灌銘同學創作。

前言

微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略,每個應用可以選擇不同的技術棧,獨立開發、獨立部署。

TinyVue組件庫的跨技術棧能力與微前端十分契合,往期我們也有文章,指導如何在wujie微前端中使用TinyVue組件庫,文章鏈接:https://mp.weixin.qq.com/s/ZqDXemh0GfnQpWACdzXdig

目前許多對微前端有需求的用戶已經在使用wujieTinyVue開發了,在使用了一段時間后,合作企業用戶和個人用戶反饋了組件庫一些問題。經過一番交流、溝通與定位,最終發現是用戶接入了微前端框架后,在特定場景下導致的一系列問題,在非微前端應用中,組件庫運行良好。

復現問題后,通過一系列排查與分析,最終總結出了四個問題:

  1. absolute 定位的彈出元素錯位,且頁面滾動不會重新定位
  2. fixed 定位的彈出元素錯位
  3. 彈出元素位置發生翻轉
  4. 表格中的 select 點擊后,下拉選項出現后馬上消失

對于以上問題,TinyVue組件庫做了相應的適配以及給用戶提供了解決方案,最終使得 TinyVue 組件良好運行在wujie微前端中。

首先來簡單介紹一下wujie微前端實現原理,wujie微前端是采用iframe+webcomponet的實現方式。通過iframes實現js沙箱能力。子應用的實例instanceiframe內運行,dom在主應用容器下的webcomponent內,通過代理 iframedocumentwebcomponent,可以實現兩者的互聯。

想要了解更多可以查看,無界微前端介紹:https://wujie-micro.github.io/doc/guide/

接下來展開說一下,收集總結的四個問題~

問題總結

問題一:absolute 定位的彈出元素錯位,且頁面滾動不會重新定位。

圖片

“彈出元素錯位”錯誤原因分析:

打開控制臺,審查元素查看樣式,看到element.sytle的第一直覺是transfrom的偏移量計算不正確,順著這個線索排查計算錯誤的原因。

圖片

排查前先簡單介紹一下TinyVue組件庫這個偏移量的計算規則:

1.找到彈出元素的 offsetParent(父定位元素),如果沒有則返回body
2.使用 getBoundingClientRect 計算 offsetParent 以及引用元素(圖中的輸入框,簡稱為reference)距離視口的位置信息。
3.以彈出元素放右邊為例,transform的左偏移量的計算規則為reference.left - offsetParent.left + reference.width

因為彈出元素的position設置為absolute,所以彈出元素的定位是根據其offsetParent計算位置的,沒有offsetParent則是根據視口來計算位置。

上述例子中,彈出元素的offsetParent為 null,因此默認返回了body作為其offsetParent,絕大部分情況下,body和視口左側和上側是對齊的,因此用body計算的偏移量,在視口上也適用。

在微前端中,子應用的body可能相對于視口有偏移。彈出元素的偏移量實際是根據body計算的,但他是非定位元素,最終導致的元素錯位。

圖片

解決方案:

既然計算規則是根據body計算的,那么將子應用將body設置為position: relative將其變為定位元素即可。

滾動不會重新定位原因分析:

首先還是簡單介紹組件庫這部分邏輯:

1.通過parentNode向上查找引用元素(輸入框)的可滾動的祖先元素(如果沒有配置冒泡則返回第一個可滾動祖先元素,否則返回所有可滾動祖先元素)
2.為步驟1獲取到的元素加上滾動方法的監聽。
3.祖先元素滾動時重新計算彈出元素的位置,使彈出元素跟隨引用元素。

但是在wujie微前端中,子應用的document再往上查找就是null了。而滾動條在主應用當中。因此主應用的滾動無法被監聽到。

圖片

解決方案:

將子應用將body設置為position: relative同樣也解決了上述問題。設置后,只有當子應用內滾動條滾動后才需要重新計算。

問題二: fixed 定位的彈出元素錯位。

在修復的問題一的情況下,依舊有部分情況會出現彈出元素錯位的 bug。并且下圖中可以看到,彈出元素從右邊翻轉到了左邊。

圖片

原因分析:

表單元素在modal中,modalfixed定位,因此表單輸入框也是fixed定位。由于引用元素是fixed定位,所以彈出元素與之相對應也應該使用fixed定位。

組件庫邏輯對于fixed定位的彈出元素偏移量的計算,在問題一提到的步驟下還增加了部分特殊處理。下面代碼是計算偏移量邏輯其中較為關鍵的一段代碼:

/*** @description 計算彈出元素的偏移量* @param el 引用元素* @param parent 彈出元素的祖先定位元素* @param fixed 彈出元素是否絕對定位* @returns 用于計算偏移量的相關信息*/
const getOffsetRectRelativeToCustomParent = (el: HTMLElement,parent: HTMLElement,fixed: boolean) => {let { top,left,width,height } = getBoundingClientRect(el)let parentRect = getBoundingClientRect(parent)if (fixed) {let { scrollTop,scrollLeft } = getScrollParent(parent)parentRect.top += scrollTopparentRect.bottom += scrollTopparentRect.left += scrollLeftparentRect.right += scrollLeft}let rect = {top: top - parentRect.top,left: left - parentRect.left,bottom: top - parentRect.top + height,right: left - parentRect.left + width,width,height}return rect
}

已上述代碼為例,上述邏輯Modal彈窗情況下,parentscrollParent都是body

21-30行代碼的目的是,為了解決在body在滾動后,parentRect.top為負數,需要加上scrollTop才是相對視口的偏移量。

但是上面的計算邏輯有個大前提,那就是body的左側和上側和視口一致,上面這段不太嚴謹的邏輯經過漫長的迭代,直到在微前端中’暴雷’。

解決方案:

position設置為fixed后,彈出元素在絕大多數情況都是相對視口定位了,但是也有特殊情況,以下是 mdn 文檔的截圖:

圖片

為了兼容上述的特殊情況,新增了getAdjustOffset方法,此方法計算相對于視口的修正偏移量,設置 top 和 left 為0,使用getBoundingClientRect計算出來的結果不為0的話,多出來的偏移量就是因為上述的 css 樣式影響了,

獲取這個修正偏移量后,后續的計算只需要加上這個偏移量,彈出元素和reference元素的位置就能夠正確對應上了。

以下是修改后的相關核心代碼:

/** 設置transform等樣式后,fixed定位不再相對于視口,* 使用1乘1px透明元素獲取fixed定位相對于視口的修正偏移量。 
**/
const getAdjustOffset = (parent: HTMLElement) => {const placeholder = document.createElement('div')setStyle(placeholder, {opacity: 0,position: 'fixed',width: 1,height: 1,top: 0,left: 0,'z-index': '-99'})parent.appendChild(placeholder)// 正常應返回 { transform: translateY( 0, left: 0 }// 否則就是被特殊的css樣式影響了const result = getBoundingClientRect(placeholder)parent.rem)oveChild(placeholder)return result
}/*** @description 計算彈出元素的偏移量* @param el 引用元素* @param parent 彈出元素的祖先定位元素* @param fixed 彈出元素是否絕對定位* @returns 用于計算偏移量的相關信息*/
const getOffsetRectRelativeToCustomParent = (el: HTMLElement,parent: HTMLElement,fixed: boolean,popper: HTMLElement
) => {let { top,left,width,height} = getBoundingClientRect(el)// 如果是fixed定位,需計算要修正的偏移量。if (fixed) {if (popper.parentElement) {const { top: adjustTop,left: adjustLeft} = getAdjustOffset(popper.parentElement)top -= adjustTopleft -= adjustLeft}return {top,left,bottom: top + height,right: left + width,width,height}}let parentRect = getBoundingClientRect(parent)let rect = {top: top - parentRect.top,left: left - parentRect.left,bottom: top - parentRect.top + height,right: left - parentRect.left + width,width,height}return rect
}

問題三:彈出元素位置發生翻轉

在問題二的截圖中除了彈出元素錯位問題,還有另外一個問題:彈出元素發生了翻轉。
圖片

原因分析:

彈出類的元素,存在一個邊界檢測邏輯,當計算出彈出元素超出邊界后,為了展示的完整性和美觀,會自動將元素翻轉。

圖片

在用戶沒有特定配置的情況下,默認的邊界為’視口’,下面是關于邊界計算邏輯的節選:

/** 計算邊界邏輯 */
const getBoundaries = (data: UpdateData,padding: number,boundariesElement: string | HTMLElement) => {// ... other codeelse if (boundariesElement === 'viewport') {let offsetParent = getOffsetParent(this._popper)let scrollParent = getScrollParent(this._popper)let offsetParentRect = getOffsetRect(offsetParent)let isFixed = data.offsets.popper.position === 'fixed'let scrollTop = isFixed ? 0 : getScrollTopValue(scrollParent)let scrollLeft = isFixed ? 0 : getScrollLeftValue(scrollParent)const docElement = window.document.documentElementboundaries = {top: 0 - (offsetParentRect.top - scrollTop),right: docElement.clientWidth - (offsetParentRect.left - scrollLeft),bottom: docElement.clientHeight - (offsetParentRect.top - scrollTop),left: 0 - (offsetParentRect.left - scrollLeft)}
}// ... other code
}

可以看到,視口的邊界計算邏輯和window.document.documentElement也就是html有關。組件庫運行在子應用中,因此這里也就是子應用的html。但在子應用中,html的寬高可能會比真實視口小得多,導致邊界計算被約束在子應用范圍當中,觸發了翻轉邏輯,導致了錯誤的翻轉。

圖片

解決方案: 組件庫對外暴露一個全局配置,用戶在子應用中可以引入全局配置,將主應用的 window賦值給全局配置的 viewportWindow 用于邊界判斷。

import globalConfig from '@opentiny/vue-renderless/common/global'// 需要判斷是否在子應用當中
if (window.__POWERED_BY_WUJIE__) {// 子應用中可以通過window.parent獲取主應用的windowglobalConfig.viewportWindow = window.parent
}

getBoundaries 方法也相對應做一下修改

/** 計算邊界邏輯 */
const getBoundaries = (data: UpdateData,padding: number,boundariesElement: string | HTMLElement) => {// ... other code// 新增代碼const viewportWindow = globalConfig.viewportWindow || windowconst docElement = viewportWindow.document.documentElementboundaries = {top: 0 - (offsetParentRect.top - scrollTop),right: docElement.clientWidth - (offsetParentRect.left - scrollLeft),bottom: docElement.clientHeight - (offsetParentRect.top - scrollTop),left: 0 - (offsetParentRect.left - scrollLeft)}// ... other code
}

問題四:表格中的select點擊后,下拉選項出現后馬上消失

圖片

原因分析:

當開啟表格編輯狀態時,表格默認處于顯示狀態,當點擊表格某一行時,會進入到編輯狀態。當點擊表格此行外的其他區域,表格就會清除編輯狀態,進入顯示狀態。

是否點擊外部是通過監聽document的點擊事件,當點擊任意元素后,都會被冒泡捕獲,組件庫使用點擊事件的event.target來判斷用戶是否點擊了表格編輯行以外的元素。

正常情況下,點擊select,event.target能夠找select對應的元素,可以正常的判斷select元素是在對應的容器中,則不會切換至顯示狀態。

圖片

wujie微前端下,點擊selectevent.target找到的是wujie-app。這個問題是瀏覽器原生的處理,詳情可以參考:https://javascript.info/shadow-dom-events 此時wujie-app不在對應的容器內,認為點擊了對應行以外的區域,因此切換至顯示狀態,下拉選項消失。

圖片

解決方案:
組件庫加入兼容邏輯,獲取 event.target 的方式修改成: (e.target.shadowRoot && e.composed) ? (e.composedPath()[0] || e.target) : e.target
加入兼容邏輯后,無論組件是否運行在微前端中,點擊事件都能找到真實點擊的dom元素,因此問題也就解決了。

結語

總體而言,上述遇到的問題主要原因有兩個,其一是 wujie 微前端中,子應用的window和視口window不是同一個。其二是webcomponent內部元素事件冒泡被外部元素捕獲時,event.target會被代理到webcomponent跟元素上導致的目標判斷錯誤。

針對問題一,整體的解決思路是要么將作用范圍限定在子應用當中,例如問題一解決方案,給子應用body加上樣式position: relative。要么是通過類似依賴注入的方式,讓相關邏輯可以正確地獲取到主應用的window
針對問題二,思路就非常明確了,目標就是要找到正確的event.target,通過加上兼容代碼后,無論是否在webcompoent中,都能正確返回event.target

當然以上提到的問題,已經在@opentiny/vue3.13.0新版本發布修復了,歡迎下載使用~

關于 OpenTiny

圖片

OpenTiny 是一套企業級 Web 前端開發解決方案,提供跨端、跨框架、跨版本的 TinyVue 組件庫,包含基于 Angular+TypeScript 的 TinyNG 組件庫,擁有靈活擴展的低代碼引擎 TinyEngine,具備主題配置系統TinyTheme / 中后臺模板 TinyPro/ TinyCLI 命令行等豐富的效率提升工具,可幫助開發者高效開發 Web 應用。


歡迎加入 OpenTiny 開源社區。添加微信小助手:opentiny-official 一起參與交流前端技術~更多視頻內容也可關注B站、抖音、小紅書、視頻號
OpenTiny 也在持續招募貢獻者,歡迎一起共建

OpenTiny 官網:https://opentiny.design/
OpenTiny 代碼倉庫:https://github.com/opentiny/
TinyVue 源碼:https://github.com/opentiny/tiny-vue
TinyEngine 源碼: https://github.com/opentiny/tiny-engine

歡迎進入代碼倉庫 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~
如果你也想要共建,可以進入代碼倉庫,找到 good first issue標簽,一起參與開源貢獻~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696266.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696266.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696266.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

springboot207基于springboot的實習管理系統

實習管理系統的設計與實現 摘要 近年來,信息化管理行業的不斷興起,使得人們的日常生活越來越離不開計算機和互聯網技術。首先,根據收集到的用戶需求分析,對設計系統有一個初步的認識與了解,確定實習管理系統的總體功…

H5星空漸變效果引導頁源碼

H5星空漸變效果引導頁源碼 源碼介紹:H5星空漸變效果引導頁源碼是一款帶有星空漸變效果的源碼,內含3個可跳轉旗下站點按鈕。 下載地址: https://www.changyouzuhao.cn/8344.html

【海賊王的數據航海:利用數據結構成為數據海洋的霸主】時間復雜度 | 空間復雜度

目錄 1 -> 算法效率 1.1 -> 如何衡量一個算法的好壞? 1.2 -> 算法的復雜度 2 -> 時間復雜度 2.1 -> 時間復雜度的概念 2.2 -> 大O的漸進表示法 2.3 -> 常見時間復雜度計算 3 -> 空間復雜度 4 -> 常見復雜度對比 1 -> 算法效…

nginx前綴匹配

nginx location ^~ /task/ { # 這樣,當您訪問 http://hostname:port/task/test 時,# 請求會被轉發到 proxy_pass /test,注意 /task/ 前綴在轉發時被去掉了。proxy_pass http://192.168.86.199:8805/; proxy_set_header Host $host; proxy…

SQL注入漏洞解析

什么是SQL注入 原理: SQL注入即是指web應用程序對用戶輸入數據的合法性沒有判斷或過濾不嚴,攻擊者可以在web應用程序中事先定義好的查詢語句的結尾上添加額外的SQL語句,在管理員不知情的情況下實現非法操作,以此來實現欺騙數據庫服…

Ps下載安裝(專業圖像處理軟件Ps安裝包下載2024【Windows版】)

Adobe全家桶下載方式 將持續更新~ 文章目錄 Adobe全家桶下載方式Ps下載方式【點我獲取下載鏈接】我們的網站一、Ps簡介聲明 Ps下載方式【點我獲取下載鏈接】 迅雷網盤下載:迅雷網盤下載方式百度網盤下載:百度網盤下載方式夸克網盤下載:夸克…

【Vuforia+Unity】AR01實現單張多張圖片識別產生對應數字內容

1.官網注冊 Home | Engine Developer Portal 2.下載插件SDK,導入Unity 3.官網創建數據庫上傳圖片,官網處理成數據 下載好導入Unity! 下載好導入Unity! 下載好導入Unity! 下載好導入Unity! 4.在Unity設…

圖——最小生成樹實現(Kruskal算法,prime算法)

目錄 預備知識: 最小生成樹概念: Kruskal算法: 代碼實現如下: 測試: Prime算法 : 代碼實現如下: 測試: 結語: 預備知識: 連通圖:在無向圖…

Sora的第一波受害者出現了。

不知道大家最近除了被Sora刷屏之外,有沒有被這張圖刷屏 我只能說網友太強大了 說實話,我進入舟老師的直播間,每次都是還有3分鐘下播,還有6單就拍完 但是10分鐘后還在激情逼單,6單之后還有6單 也許在營銷學上&#x…

深入理解nginx的動態變量機制【上】

目錄 1. 概述2. 動態變量的分類2.1 按照變量名的確定性來分類2.2 按照變量聲明的來源分類2.3 按照是否可以變更分類2.4 按照是否可以緩存分類2.5 按照變量的索引方式分類 3. 變量的使用3.1 聲明一個變量3.1.1 支撐變量聲明的nginx關鍵結構體3.1.2 在配置文件中聲明3.1.3 在http…

C++筆記:OOP三大特性之多態

前言 本博客中的代碼和解釋都是在VS2019下的x86程序中進行的,涉及的指針都是 4 字節,如果要其他平臺下測試,部分代碼需要改動。比如:如果是x64程序,則需要考慮指針是8bytes問題等等。 文章目錄 前言一、多態的概念二、…

【C++初階】系統實現日期類

目錄 一.運算符重載實現各個接口 1.小于 (d1)<> 2.等于 (d1d2) 3.小于等于&#xff08;d1<d2&#xff09; 4.大于&#xff08;d1>d2&#xff09; 5.大于等于&#xff08;d1>d2&#xff09; 6.不等于&#xff08;d1!d2&#xff09; 7.日期天數 (1) 算…

mac圖片怎么轉換格式jpg?四種高效方法助你輕松搞定JPG格式

mac圖片怎么轉換格式jpg&#xff1f;在數字時代&#xff0c;圖片格式的轉換成為了我們日常操作中的一項基本技能。特別是在使用Mac操作系統的用戶中&#xff0c;如何將圖片轉換為JPG格式成為了一個熱門話題。本文將為你詳細介紹四種簡單實用的方法&#xff0c;幫助你在Mac上輕松…

測試基礎1:偉大航路喲呼(Linux基礎、mysql基礎)

1 測試流程和方法 軟件測試定義&#xff1a; 從方式上看&#xff1a;包含人工測試、自動化測試 從方法上看&#xff1a;運行程序或系統和測定程序或系統的過程 從目的上看&#xff1a;包括找bug和找bug出現的原因 軟件測試的原則&#xff1a;功能性、可靠性、易用性、效率性…

一、網絡基礎知識

1、IP地址和端口號 1.1、IP地址 定義&#xff1a;用于在網絡中唯一標識設備的地址。格式&#xff1a;通常由四個數字組成&#xff0c;以點分十進制表示&#xff0c;例如&#xff1a;192.168.0.1。(IPv4)作用&#xff1a;允許網絡中的設備相互通信&#xff0c;通過IP地址可以定…

Python 數據可視化之密度散點圖 Density Scatter Plot

&#x1f349; CSDN 葉庭云&#xff1a;https://yetingyun.blog.csdn.net/ 密度散點圖&#xff08;Density Scatter Plot&#xff09;&#xff0c;也稱為密度點圖或核密度估計散點圖&#xff0c;是一種數據可視化技術&#xff0c;主要用于展示大量數據點在二維平面上的分布情況…

Swift基礎知識:24.Swift可選鏈

在 Swift 中&#xff0c;可選鏈&#xff08;Optional Chaining&#xff09;是一種用于調用可選類型屬性、方法或下標的安全方式。可選鏈允許我們在調用鏈中的任何一個屬性、方法或下標返回 nil 時&#xff0c;整個調用鏈仍然可以繼續執行&#xff0c;而不會因為其中的任何一個可…

一樣的代碼不同項目跳轉頁面報404的解決辦法

今天收到實施反饋的一個問題&#xff0c;點項目名稱跳轉項目詳情頁面時&#xff0c;有的頁面跳轉顯示正常&#xff0c;有的頁面跳轉報404錯誤。錯誤如下&#xff1a; 發現報錯的項目都有一個共性就是有特殊字符“[ ]” , 解決的辦法就是把帶有特殊字符的字段 用 encodeURI()…

Java SE 入門到精通—4.抽象類與接口【Java】

抽象類 同接口一樣&#xff0c;用來約束子類&#xff0c;限制子類必須擁有某些方法&#xff0c;比普通類多了個抽象方法&#xff0c;用抽象方法該類必為抽象類 概念 沒有具體的對象&#xff0c;具體的方法的一個類 abstract關鍵字聲明為抽象類/方法 一個類中有抽象方法則該…

統計前端傳過來的Req的非空屬性個數的工具類

背景 日常開發中&#xff0c;我們通常會根據前端傳過來的實體類的屬性個數去做邏輯判斷&#xff0c;下面的是判斷屬性個數的工具類。 工具類 public static Integer nonNullFieldCount(Req req) {if (req null) {return 0;}int nonNullFieldCount 0;Field[] fields req.ge…