如何处理 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 打开其他人的页面,都不会再遇到缩放问题了,是不是很帅。

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


目录
相关文章
|
小程序 开发工具 git
【微信小程序】-- uni-app 项目--- 购物车 -- 配置 tabBar 效果(五十一)
【微信小程序】-- uni-app 项目--- 购物车 -- 配置 tabBar 效果(五十一)
|
资源调度 前端开发 算法
前端依赖版本重写指南
感谢神奇的 Semver 动态规则,npm 社区经常会发生依赖包更新后引入破坏变更的情况(应用没有使用依赖锁的话),而应用开发者就要在自己的依赖声明里先临时绕过,避免安装到有问题的版本,如果是一级依赖,只需要改 package.json 的声明就可以了,但如果是子依赖,就需要进行版本重写(overrides/resolution)了。本文是一篇针对版本重写功能的指南性文章,当你遇到如下的问题时,就可以按照对应的依赖重写语法,解决这些依赖问题了。
8626 1
前端依赖版本重写指南
|
移动开发 JavaScript
H5唤起手机打电话(拨号)和发短信功能
H5唤起手机打电话(拨号)和发短信功能
1291 0
|
8月前
|
移动开发 开发工具 Android开发
Uniapp与原生App集成的流程
Uniapp与原生App集成的流程
3629 156
|
6月前
|
人工智能 NoSQL Java
Spring AI 进阶之路03:集成RAG构建高效知识库
本文介绍如何在Spring Boot中集成RAG(检索增强生成)技术,通过Redis向量数据库为大模型外挂私域知识库。手把手实现文档上传、切分、向量化存储,并构建支持普通对话与知识库问答双模式的智能聊天机器人,解决大模型对私有信息无知的问题,助力打造企业级AI应用。
1885 1
|
Unix 网络安全 数据安全/隐私保护
putty Faual Error:No supported authentication methods available (server sent: publickey)
putty Faual Error:No supported authentication methods available (server sent: publickey)
3780 0
element中tree组件的选中获取和返显
本文介绍了如何在Element UI的tree组件中获取选中的节点值以及如何进行节点的默认选中(返显)。主要通过使用`getCheckedKeys()`和`getHalfCheckedKeys()`方法来获取完全和半选中的节点,然后使用`setCheckedKeys()`方法来设置默认选中的节点。
2022 2
element中tree组件的选中获取和返显
|
存储 监控 前端开发
Sentry 监控部署与使用(详细流程)
Sentry 监控部署与使用(详细流程)
15279 1
Vue3日期选择器(DatePicker)
该组件基于 **@vuepic/vue-datepicker@9.0.1** 进行二次封装,简化了日常使用。除范围和年选择器外,其他日期选择均返回格式化的字符串。提供了多种自定义设置,如日期选择器宽度、模式、格式等,并支持时间选择和“今天”按钮展示。详细配置及更多功能请参考[官方文档](https://vue3datepicker.com/installation/)。组件已集成所有原生属性,并支持主题颜色自定义。 示例代码展示了如何创建和使用日期选择器组件,包括基本使用、禁用日期、日期时间选择器、范围选择器等多种场景。更多功能和样式可通过官方文档了解。
4035 2
Vue3日期选择器(DatePicker)
|
移动开发 资源调度 JavaScript
Vue移动端网页(H5)预览pdf文件(pdfh5和vue-pdf)
这篇文章介绍了在Vue移动端网页中使用`pdfh5`和`vue-pdf`两个插件来实现PDF文件的预览,包括滚动查看、缩放、添加水印、分页加载、跳转指定页数等功能。
11725 1
Vue移动端网页(H5)预览pdf文件(pdfh5和vue-pdf)