扬州手机网站建设,跨境电商网站开发,阿里云 wordpress 区别,怎么去掉wordpress底部本文由体验技术团队 TinyVue 项目成员岑灌铭同学创作。
前言
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略#xff0c;每个应用可以选择不同的技术栈#xff0c;独立开发、独立部署。
TinyVue组件库的跨技术栈能力与微前端十…本文由体验技术团队 TinyVue 项目成员岑灌铭同学创作。
前言
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略每个应用可以选择不同的技术栈独立开发、独立部署。
TinyVue组件库的跨技术栈能力与微前端十分契合往期我们也有文章指导如何在wujie微前端中使用TinyVue组件库文章链接https://mp.weixin.qq.com/s/ZqDXemh0GfnQpWACdzXdig
目前许多对微前端有需求的用户已经在使用wujie和TinyVue开发了在使用了一段时间后合作企业用户和个人用户反馈了组件库一些问题。经过一番交流、沟通与定位最终发现是用户接入了微前端框架后在特定场景下导致的一系列问题在非微前端应用中组件库运行良好。
复现问题后通过一系列排查与分析最终总结出了四个问题
absolute 定位的弹出元素错位且页面滚动不会重新定位fixed 定位的弹出元素错位弹出元素位置发生翻转表格中的 select 点击后下拉选项出现后马上消失
对于以上问题TinyVue组件库做了相应的适配以及给用户提供了解决方案最终使得 TinyVue 组件良好运行在wujie微前端中。
首先来简单介绍一下wujie微前端实现原理wujie微前端是采用iframewebcomponet的实现方式。通过iframes实现js沙箱能力。子应用的实例instance在iframe内运行dom在主应用容器下的webcomponent内通过代理 iframe的document到webcomponent可以实现两者的互联。
想要了解更多可以查看无界微前端介绍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中modal是fixed定位因此表单输入框也是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弹窗情况下parent和scrollParent都是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 fixedlet 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来判断用户是否点击了表格编辑行以外的元素。
正常情况下点击selectevent.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/vue的3.13.0新版本发布修复了欢迎下载使用~
关于 OpenTiny OpenTiny 是一套企业级 Web 前端开发解决方案提供跨端、跨框架、跨版本的 TinyVue 组件库包含基于 AngularTypeScript 的 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
欢迎进入代码仓库 StarTinyEngine、TinyVue、TinyNG、TinyCLI~ 如果你也想要共建可以进入代码仓库找到 good first issue标签一起参与开源贡献~