问题
相信不少的朋友都遇到这样的场景,在我们的系统中嵌入别人的页面,和在别人的系统中嵌入我们的页面。这里的第一原则是,不管是我们嵌入别人还是别人嵌入我们,我们都只能修改我们自己的页面,而其他别人的页面,我们无权修改,毕竟会有“我们嵌入其他系统的时候,都是好好的啊”和“其他人嵌入我们的系统的时候,都是可以的。”
解法
这里需要分开讨论。
我们的页面嵌入其他系统
这里有一个 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
打开其他人的页面,都不会再遇到缩放问题了,是不是很帅。
当然这只是理想的情况,毕竟这是我昨天写的代码,我只能保证它在昨天能够正确运行。