如何处理 h5 使用 iframe 嵌套页面,内外 viewport 不一致导致的缩放问题?

简介: 如何处理 h5 使用 iframe 嵌套页面,内外 viewport 不一致导致的缩放问题?

问题

相信不少的朋友都遇到这样的场景,在我们的系统中嵌入别人的页面,和在别人的系统中嵌入我们的页面。这里的第一原则是,不管是我们嵌入别人还是别人嵌入我们,我们都只能修改我们自己的页面,而其他别人的页面,我们无权修改,毕竟会有“我们嵌入其他系统的时候,都是好好的啊”和“其他人嵌入我们的系统的时候,都是可以的。”



解法

这里需要分开讨论。


我们的页面嵌入其他系统

这里有一个 iframe 的特性需要事先知晓,当内外 viewport 不一致时,将会使用主页面的 viewport。


比如,在 alita 项目中,我们使用的是现在比较稳定的高清方案。在项目中使用rem单位,通过获取设备的 dpr 即 window.devicePixelRatio,将项目中的 rem 值设置成真实的 px 值。

doc.documentElement.style.fontSize = 100 / 2 * dpr  + 'px';

然后通过设置 viewport 的 initial-scale 来进行缩放页面达到高清显示页面的效果。


比如 iphone6 中 dpr 是2,我们就会将 px 设置成 @2x,然后通过设置 initial-scale=0.5,来实现页面的适配。


通常出现内外 viewport 不一致的情况,是主系统没有使用类似的高清方案,viewport 的 initial-scale 一般是设置为 1。其实就算设置的是其他的值也没有关系,我们都可以通过在代码中获取到主页面真实的 viewport。

if (window != top ) {
    const meta = document.querySelector('meta[name="viewport"]');
    const metaStr = meta.getAttribute('content') || '';
    const viewport = getViewPort(metaStr);
   if (viewport['initial-scale']) {
          const dpr = window.devicePixelRatio || 1;
          const baseScale = 10 / dpr;
          window.alitaFontScale = baseScale / parseInt(`${parseFloat(viewport['initial-scale']) * 10}`, 10);
    }
}


先判断一下,我们的页面是在 iframe 中打开,然后用 js 的方法获取到当前页面的 viewport。这里需要注意我们前面提到的“当内外 viewport 不一致时,将会使用主页面的 viewport”。所以在这里获取到的是主页面的 viewport。


(这里的几个类型转换,纯属是我个人的强迫症,其实没有什么意义。)

到此为止,我们就获得了我们也需要的缩放比例,将这个值放入我们最开始的 base fontsize 的计算中,就可以处理缩放问题了。

doc.documentElement.style.fontSize = 100 / 2 * dpr * window.alitaFontScale  + 'px';


其他的页面嵌入我们的页面中。

理解了上面的逻辑,那要处理这个问题就比较容易了。如果其他页面也使用类似的高清方案或者其他适配方案,理论上他们会处理这样的异常情况。在我们实际交互场景中,我们只遇到子系统 initial-scale=1 的情况,因此我这里也仅仅针对这个情况来解决问题。


(这也仅仅是我个人的设计理念问题,做的少就是做的多。)

比如在iphone 6 上,我们使用的是 @2x 的 px 值,但是子系统用的是 @1x 。因此我们只需要将 iframe 的大小设置成我们需要的 1 半,然后通过 transform scale 放大一倍就可以解决。


const Iframe: FC<IframeProps> = (props) => {
  const stt = {
    border: 'none',
    width: `50vw`,
    height: `50vh`,
    transformOrigin: '0 0',
    transform: `scale(2)`,
  };
  return <iframe style={stt} src={url}></iframe>;
};


套娃模式

在有其他系统嵌入在我们的系统的情况下,我们的系统嵌入到其他系统中。(这里有点绕,反正就是嵌套里面又嵌套) 不知道你有没有留意到,在前面的实现中我们使用了一个全局变量,window.alitaFontScale。可以通过它来判断是否是套娃,和我们项目当前已经做出的修改情况。

import React from 'react';
import type { FC, IframeHTMLAttributes } from 'react';
interface AlitaIframeProps extends IframeHTMLAttributes<HTMLIFrameElement> {
  /**
   * 0~100
   */
  width?: number;
  /**
   * 0~100
   */
  height?: number;
}
const AlitaIframe: FC<AlitaIframeProps> = (props) => {
  const { width = 100, height = 100, style, ...rest } = props;
  (window as any).alitaFontScale = (window as any).alitaFontScale ?? 1;
  const defaultStyle = {
    border: 'none',
    ...style,
    width: `${1 / (window as any).alitaFontScale * 50 * width / 100}vw`,
    height: `${1 / (window as any).alitaFontScale * 50 * height / 100}vh`,
    transformOrigin: '0 0',
    transform: `scale(${(window as any).alitaFontScale / 0.5})`,
  };
  return <iframe style={defaultStyle} {...rest}></iframe>;
};
export default AlitaIframe;



其他

本着 alita 体系的设计理念,框架中做的越多,实际交付开发中就写的越少。

我将第一种情况内收到了 hd 的插件中,适用于 alita 项目和其他使用了 @alitajs/hd 插件的 umi 项目。框架自己兼容,用户无需理会差异。

将第二种情况,发布到了 @alitajs/iframe 组件。在项目中可以直接引入使用,用户无需理会过多的逻辑。

$ yarn add @alitajs/iframe
or
$ npm i @alitajs/iframe
import React from 'react';
import Iframe  from '@alitajs/iframe';
const otherUrl = 'http://localhost:8000/#/';
export default () => <Iframe src={otherUrl} />;



总结

解法固然重要,更有趣的是过程。其实我们在实际开发中,都会遇到一些问题,或简单或复杂,或通用或局限,但是如果能够把问题梳理清楚,将代码保留在更高一个层级的位置,服务自己,服务他人,甚至能让整个团队受益。

想象一下,以后在 React 项目中,只要使用 Iframe 打开其他人的页面,都不会再遇到缩放问题了,是不是很帅。

当然这只是理想的情况,毕竟这是我昨天写的代码,我只能保证它在昨天能够正确运行。


目录
相关文章
|
负载均衡 Ubuntu 应用服务中间件
|
Rust JavaScript Unix
Nodejs 常见版本管理工具(nvm、n、fnm、nvs、nodenv)
Nodejs 常见版本管理工具(nvm、n、fnm、nvs、nodenv)
12035 0
|
Web App开发 开发框架 前端开发
移动端window.open跳转链接时,iOS没有反应的问题
【10月更文挑战第9天】在移动端使用 `window.open` 跳转链接时,iOS 可能无响应,原因是 iOS 的安全策略和弹出窗口阻止功能。解决方法包括:确保在用户交互后触发 `window.open`,将目标设置为 `_self`,使用锚点链接模拟跳转,或利用专门的移动端框架。需综合考虑这些方案以优化用户体验。
1990 61
|
JavaScript 前端开发 jenkins
【前端】vue项目打包Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest解决方案
【前端】vue项目打包Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest解决方案
3081 0
|
8月前
|
数据采集 前端开发 JavaScript
PDF预览:利用vue3-pdf-app实现前端PDF在线展示
通过本文的介绍,我们详细了解了如何在Vue3项目中使用vue3-pdf-app实现PDF文件的在线展示。从项目初始化、插件集成到高级功能的实现和部署优化,希望对你有所帮助。在实际项目中,灵活运用这些技术可以大大提升用户体验和项目质量。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
Web App开发 iOS开发 容器
Vue3PDF预览(vue3-pdf-app)
`vue3-pdf-app` 插件提供了一个简单而强大的 PDF 预览解决方案。通过 `&lt;a&gt;` 标签即可快速预览 PDF 文件。为满足更复杂的定制需求,提供了 `PDFViewer.vue` 组件,基于 `vue3-pdf-app@1.0.3` 封装,支持多种功能如缩放、旋转、全屏预览、打印等,并可自定义主题颜色与语言。组件属性包括文件地址 (`src`)、预览容器尺寸 (`width`, `height`)、默认缩放规则 (`pageScale`) 和主题 (`theme`) 等。适用于多种浏览器,方便集成到项目中。
2797 2
Vue3PDF预览(vue3-pdf-app)
在Vue3项目中使用 vue3-seamless-scroll 无缝滚动插件
本文介绍了如何在Vue3项目中使用`vue3-seamless-scroll`插件实现无缝滚动效果,并提供了详细的示例代码和运行效果。
5886 0
|
JavaScript 前端开发 API
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
本文介绍了如何在Vue 3项目中使用v-md-editor组件库来创建markdown编辑器和预览组件。文章提供了安装步骤、如何在main.js中进行全局配置、以及如何在页面中使用VMdEditor和VMdPreview组件的示例代码。此外,还提供了一个完整示例的链接,包括编辑器和预览组件的使用效果和代码。
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
|
12月前
Cursor + qwen2.5-coder 32b 的配置方式
安装Cursor后,进入设置修改OpenAI基础URL为阿里云的DashScope接口,并添加Qwen2.5-Coder 32B模型。需先访问阿里云百灵控制台申请免费Key。配置完成后,即可使用该模型进行开发和测试。
8306 2
|
JavaScript
基于Vue2或Vue3实现任意上下左右拖拽悬浮的元素,且配置为自定义的全局指令
这篇文章介绍了如何在Vue 2或Vue 3项目中实现一个自定义的全局指令`v-dragSwitch`,用于创建可以任意方向拖拽并悬浮的元素,同时包含边界处理的逻辑。
3772 2
基于Vue2或Vue3实现任意上下左右拖拽悬浮的元素,且配置为自定义的全局指令