目录背景介绍CSS Position: Fixed 的失效问题问题描述实际开发中的影响问题示例uni-app Vue3 中的跨平台解决方案条件编译实现各端实现原理与细节1. H5 环境 - Teleport 实现原理2. 小程序环境 - root-portal 实现原理3. App 环境 - renderjs 实现原理统一封装实现背景介绍在 uni-app 开发中弹窗、抽屉、下拉菜单等覆盖型组件是非常常见的交互元素。这些组件通常需要相对于视口定位不受父元素影响。然而在某些场景下 Position: Fixed 会有不符合预期的表现题。而且 uni-app 一次编码多端报错的特性导致这个问题十分棘手因此今天我们就要探讨一个跨端的解决方案来处理这些定位问题。CSS Position: Fixed 的失效问题问题描述根据 CSS 规范position: fixed元素的定位上下文默认是相对于视口viewport的。但在以下情况下定位上下文会发生改变Transform 属性当祖先元素应用了transform属性时Filter 效果当祖先元素设置了filter或backdrop-filter属性时3D 渲染上下文当祖先元素设置了perspective属性时will-change当祖先元素的will-change属性设置为上述值时这种行为在 MDN 文档中有明确说明当元素祖先的 transform、perspective、filter 或 backdrop-filter 属性非 none 时容器由视口改为该祖先。—— MDN - position: fixed实际开发中的影响这个问题在实际开发中经常会带来以下困扰模态框定位异常在带有变换效果的容器中模态框无法相对于视口居中弹窗位置会随父容器滚动而改变固定导航失效使用 CSS transform 实现动画效果的页面中固定导航栏会失去固定效果在滚动时导航栏可能会跟随内容移动交互组件错位下拉菜单、提示框等定位不准确遮罩层无法完全覆盖视口问题示例template view styletransform: scale(1.8) view styleposition: fixed; top: 0; left: 0; 这个元素不会固定在视口顶部1 /view /view /templateuni-app Vue3 中的跨平台解决方案我们期望针对 uni-app Vue3 提供了一个优雅的跨平台解决方案。通过条件编译和平台特定的实现组件能够在不同端完美运行。核心思路是将内容传送到应用根节点从而避免中间层级的 CSS 上下文影响。条件编译实现使用 uni-app 的条件编译特性我们可以为不同平台提供最优的实现方案template !-- #ifdef H5 -- teleport tobody slot / /teleport !-- #endif -- !-- #ifdef MP-WEIXIN || MP-ALIPAY -- root-portal slot / /root-portal !-- #endif -- slot / /template script modulerender langrenderjs export default { mounted() { // 获取根节点 const root document.querySelector(uni-app) || document.body if (this.$ownerInstance.$el) { root.appendChild(this.$ownerInstance.$el) } } } /script style /style各端实现原理与细节1. H5 环境 - Teleport 实现原理Teleport是一个内置组件它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。Teleport接收一个toprop 来指定传送的目标。to的值可以是一个 CSS 选择器字符串也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到body标签下”。// teleport 在 uni-app H5 端的工作方式 // 1. 组件逻辑 const show ref(false) // 2. 模板中使用 teleport tobody view v-ifshow classpopup slot / /view /teleportTIPTeleport挂载时传送的to目标必须已经存在于 DOM 中。理想情况下这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的你需要确保在挂载Teleport之前先挂载该元素。Teleport只改变了渲染的 DOM 结构它不会影响组件间的逻辑关系。也就是说如果Teleport包含了一个组件那么该组件始终和这个使用了Teleport的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。这也意味着来自父组件的注入也会按预期工作子组件将在 Vue Devtools 中嵌套在父级组件下面而不是放在实际内容移动到的地方。优点完全复用 Vue3 的能力支持动态目标节点保持组件状态和事件绑定2. 小程序环境 - root-portal 实现原理使整个子树从页面中脱离出来类似于在 CSS 中使用 fixed position 的效果。主要用于制作弹窗、弹出层等。属性类型默认值必填说明最低版本enablebooleantrue否是否从页面中脱离出来2.26.1externalClassstring否外部样式类3.9.2在开发者工具中预览效果https://developers.weixin.qq.com/s/1XCYEQmb7UJyroot-portal view classpopup slot / /view /root-portal3. App 环境 - renderjs 实现原理App 端使用renderjs实现节点操作这是一个强大的跨平台解决方案直接运行在视图层Webview中可以访问完整的浏览器 API支持直接 DOM 操作设置 script 节点的 lang 为 renderjs视图层和逻辑层通讯方式与 WXS 一致另外可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。this.$ownerInstance.callMethod() 仅支持调用逻辑层vue选项式中的 methods 中定义的方法。// App 端的实现 script modulerender langrenderjs export default { mounted() { // 获取根节点 const root document.querySelector(uni-app) || document.body if (this.$ownerInstance.$el) { root.appendChild(this.$ownerInstance.$el) } } } /script优点大幅降低逻辑层和视图层的通讯损耗提供高性能视图交互能力uni-app的app端逻辑层和视图层是分离的这种机制有很多好处但也有一个副作用是在造成了两层之间通信阻塞。尤其是App的Android端阻塞问题影响了高性能应用的制作。renderjs运行在视图层可以直接操作视图层的元素避免通信折损。在hello uni-app的canvas示例中App端使用了renderjs由运行在视图层的renderjs直接操作视图层的canvas实现了远超微信小程序的流畅canvas动画示例。具体在hello uni-app示例中体验对比App端和小程序端的性能差异。在视图层操作dom运行for web的js库官方不建议在uni-app里操作dom但如果你不开发小程序想使用一些操作了dom、window的库其实可以使用renderjs来解决。在app-vue环境下视图层由webview渲染而renderjs运行在视图层自然可以操作dom和window。这是一个基于renderjs运行echart完整版的示例renderjs版echart同理f2、threejs等库都可以这么用。nvue的视图层是原生的无法运行js。但提供了bindingx技术来解决通信阻塞。详见微信小程序下替代方案是wxs这是微信提供的一个裁剪版renderjs。详见web下不存在逻辑层和视图层的通信阻塞也可以直接操作dom所以在web端使用renderjs主要是为了跨端复用代码。如果只开发web端没有必要使用renderjs。统一封装实现为了统一管理这三种实现方式我们会将其封装一个统一的组件在 WotUI 组件库中提供。这个统一封装使用条件编译区分平台保持一致的 API 和使用方式解决了跨平台兼容性问题支持微信小程序、支付宝小程序、APP和h5原文地址参考作者不如摸鱼去链接https://juejin.cn/post/7526693939107102720来源稀土掘金
uni-app 弹窗总被父元素“绑架”?3招破局,H5/小程序/APP一招通杀!
目录背景介绍CSS Position: Fixed 的失效问题问题描述实际开发中的影响问题示例uni-app Vue3 中的跨平台解决方案条件编译实现各端实现原理与细节1. H5 环境 - Teleport 实现原理2. 小程序环境 - root-portal 实现原理3. App 环境 - renderjs 实现原理统一封装实现背景介绍在 uni-app 开发中弹窗、抽屉、下拉菜单等覆盖型组件是非常常见的交互元素。这些组件通常需要相对于视口定位不受父元素影响。然而在某些场景下 Position: Fixed 会有不符合预期的表现题。而且 uni-app 一次编码多端报错的特性导致这个问题十分棘手因此今天我们就要探讨一个跨端的解决方案来处理这些定位问题。CSS Position: Fixed 的失效问题问题描述根据 CSS 规范position: fixed元素的定位上下文默认是相对于视口viewport的。但在以下情况下定位上下文会发生改变Transform 属性当祖先元素应用了transform属性时Filter 效果当祖先元素设置了filter或backdrop-filter属性时3D 渲染上下文当祖先元素设置了perspective属性时will-change当祖先元素的will-change属性设置为上述值时这种行为在 MDN 文档中有明确说明当元素祖先的 transform、perspective、filter 或 backdrop-filter 属性非 none 时容器由视口改为该祖先。—— MDN - position: fixed实际开发中的影响这个问题在实际开发中经常会带来以下困扰模态框定位异常在带有变换效果的容器中模态框无法相对于视口居中弹窗位置会随父容器滚动而改变固定导航失效使用 CSS transform 实现动画效果的页面中固定导航栏会失去固定效果在滚动时导航栏可能会跟随内容移动交互组件错位下拉菜单、提示框等定位不准确遮罩层无法完全覆盖视口问题示例template view styletransform: scale(1.8) view styleposition: fixed; top: 0; left: 0; 这个元素不会固定在视口顶部1 /view /view /templateuni-app Vue3 中的跨平台解决方案我们期望针对 uni-app Vue3 提供了一个优雅的跨平台解决方案。通过条件编译和平台特定的实现组件能够在不同端完美运行。核心思路是将内容传送到应用根节点从而避免中间层级的 CSS 上下文影响。条件编译实现使用 uni-app 的条件编译特性我们可以为不同平台提供最优的实现方案template !-- #ifdef H5 -- teleport tobody slot / /teleport !-- #endif -- !-- #ifdef MP-WEIXIN || MP-ALIPAY -- root-portal slot / /root-portal !-- #endif -- slot / /template script modulerender langrenderjs export default { mounted() { // 获取根节点 const root document.querySelector(uni-app) || document.body if (this.$ownerInstance.$el) { root.appendChild(this.$ownerInstance.$el) } } } /script style /style各端实现原理与细节1. H5 环境 - Teleport 实现原理Teleport是一个内置组件它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。Teleport接收一个toprop 来指定传送的目标。to的值可以是一个 CSS 选择器字符串也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到body标签下”。// teleport 在 uni-app H5 端的工作方式 // 1. 组件逻辑 const show ref(false) // 2. 模板中使用 teleport tobody view v-ifshow classpopup slot / /view /teleportTIPTeleport挂载时传送的to目标必须已经存在于 DOM 中。理想情况下这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的你需要确保在挂载Teleport之前先挂载该元素。Teleport只改变了渲染的 DOM 结构它不会影响组件间的逻辑关系。也就是说如果Teleport包含了一个组件那么该组件始终和这个使用了Teleport的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。这也意味着来自父组件的注入也会按预期工作子组件将在 Vue Devtools 中嵌套在父级组件下面而不是放在实际内容移动到的地方。优点完全复用 Vue3 的能力支持动态目标节点保持组件状态和事件绑定2. 小程序环境 - root-portal 实现原理使整个子树从页面中脱离出来类似于在 CSS 中使用 fixed position 的效果。主要用于制作弹窗、弹出层等。属性类型默认值必填说明最低版本enablebooleantrue否是否从页面中脱离出来2.26.1externalClassstring否外部样式类3.9.2在开发者工具中预览效果https://developers.weixin.qq.com/s/1XCYEQmb7UJyroot-portal view classpopup slot / /view /root-portal3. App 环境 - renderjs 实现原理App 端使用renderjs实现节点操作这是一个强大的跨平台解决方案直接运行在视图层Webview中可以访问完整的浏览器 API支持直接 DOM 操作设置 script 节点的 lang 为 renderjs视图层和逻辑层通讯方式与 WXS 一致另外可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。this.$ownerInstance.callMethod() 仅支持调用逻辑层vue选项式中的 methods 中定义的方法。// App 端的实现 script modulerender langrenderjs export default { mounted() { // 获取根节点 const root document.querySelector(uni-app) || document.body if (this.$ownerInstance.$el) { root.appendChild(this.$ownerInstance.$el) } } } /script优点大幅降低逻辑层和视图层的通讯损耗提供高性能视图交互能力uni-app的app端逻辑层和视图层是分离的这种机制有很多好处但也有一个副作用是在造成了两层之间通信阻塞。尤其是App的Android端阻塞问题影响了高性能应用的制作。renderjs运行在视图层可以直接操作视图层的元素避免通信折损。在hello uni-app的canvas示例中App端使用了renderjs由运行在视图层的renderjs直接操作视图层的canvas实现了远超微信小程序的流畅canvas动画示例。具体在hello uni-app示例中体验对比App端和小程序端的性能差异。在视图层操作dom运行for web的js库官方不建议在uni-app里操作dom但如果你不开发小程序想使用一些操作了dom、window的库其实可以使用renderjs来解决。在app-vue环境下视图层由webview渲染而renderjs运行在视图层自然可以操作dom和window。这是一个基于renderjs运行echart完整版的示例renderjs版echart同理f2、threejs等库都可以这么用。nvue的视图层是原生的无法运行js。但提供了bindingx技术来解决通信阻塞。详见微信小程序下替代方案是wxs这是微信提供的一个裁剪版renderjs。详见web下不存在逻辑层和视图层的通信阻塞也可以直接操作dom所以在web端使用renderjs主要是为了跨端复用代码。如果只开发web端没有必要使用renderjs。统一封装实现为了统一管理这三种实现方式我们会将其封装一个统一的组件在 WotUI 组件库中提供。这个统一封装使用条件编译区分平台保持一致的 API 和使用方式解决了跨平台兼容性问题支持微信小程序、支付宝小程序、APP和h5原文地址参考作者不如摸鱼去链接https://juejin.cn/post/7526693939107102720来源稀土掘金