
需求
在一个h5的移动端项目中,需要支持设置多种字体大小,实现方案是通过webview来实现的,见文章:移动端字体放大问题的研究。简单来说,iOS中,通过设置body标签style的-webkit-text-size-adjust 值来实现,例如设置为137.5%,则在safari内核中的浏览器展示页面,所有DOM的font-size都会被放大37.5%;而在android中,则是通过android-webview的setTextZoom这个api来实现的。
问题
前端如何获取到当前页面设置的字体大小类型?
方式1: 通过原生app
最简单的方式,就是由app的同学告诉我们,当前字体的缩放类型,例如,可以放置在每一个页面的url中,添加一个查询参数,该参数的值是约定的字体类型值,例如:
/home?fs=[sm,nm,md,lg]
其中sm表示小号,nm表示正常,md表示略大,lg表示超大。前端同学就可以通过解析url,来获取当前的字体类型。
又或者可以通过下发事件的方式,当页面被初始化的时候,触发事件,将具体的字体大小类型传给我们,如在前端页面中,我们可以这样监听app下发的事件
document.addEventListener('fontsizeInit',handle)
其中fontsizeInit与app同学约定好的事件名称,handle函数则是具体的取参逻辑等。
方式2: 前端自行判断
在一些第三方的webview中,无法要求他们在告知前端页面的当前字体大小类型,就可以通过前端自行来判断,本质的原理是根据android与iOS实现不同,具体来获取,也是较灵活的一种方式。
思路是,可以提取这部分逻辑成一个公用hook,然后在全局文件中(例如baseLayout.js全局layout中)使用,获取到当前的字体类型,然后注入给需要的组件。
如baseLayout.js:
import React, { useLayoutEffect, useMemo } from 'react';
import useFontSize from './useFontSize';
import styles from './BaseLayout.less';
const GlobalContext = React.createContext({});
const BaseLayout = (props) => {
const { dispatch, children } = props;
const [scale, sizeType] = useFontSize();
const value = useMemo(
() => ({
// 字体放大
sizeType, // 字体类型
scale, // 放大比例
}),
[scale, sizeType]
);
return (
<GlobalContext.Provider
// 全局配置context
value={value}
>
<div className={styles.root}>{children}</div>
</GlobalContext.Provider>
);
};
export default BaseLayout;
而具体的useFontSize的代码判断逻辑:
import { useState, useEffect } from 'react';
import { isAndroid, isIOS } from '@/utils';
const Sized = { // 字体类型枚举
i: 'i', // 初始状态
sm: 'sm', // 小号
nm: 'nm',// 默认
md: 'md',// 大号
lg: 'lg',// 超大号
};
const SizedIOS = { // ios的webkitTextSizeAdjust值
87.5: Sized.sm, // 87.5%
100: Sized.nm, // 100%
118.75: Sized.md, // 118.75%
137.5: Sized.lg, // 137.5%
};
const SizedAndroid = { // android的字体缩放比例
0.875: Sized.sm, // 0.875
1: Sized.nm, // 1
1.1875: Sized.md, // 1.1875
1.375: Sized.lg, // 1.375
};
const useFontSize = () => {
const [sizeType, setSizeType] = useState(Sized.i); // 字体类型
const [scale, setScale] = useState(1); // 放大的比例
useEffect(() => {
if (isIOS) {
const callback = () => {
let webkitTextSizeAdjust = Number(
document.body.getAttribute('style')?.match(/\d+\.+\d+/gi)?.[0]
);
if (
Number.isNaN(webkitTextSizeAdjust) ||
webkitTextSizeAdjust / 100 === scale
) {
// no changed
return;
}
webkitTextSizeAdjust = webkitTextSizeAdjust || 100; // set default Value
window.console.log('webkitTextSizeAdjust', webkitTextSizeAdjust);
setSizeType(SizedIOS[webkitTextSizeAdjust] || Sized.nm);
setScale(webkitTextSizeAdjust / 100, 1); // ios 需要除百分比
};
const observer = new window.MutationObserver((mutations) => {
mutations.forEach(() => {
callback();
});
});
observer.observe(document.body, {
attributes: true,
attributeFilter: ['style'],
});
callback();
}
if (isAndroid) {
const div = document.createElement('div');
div.style = 'font-size:16px;';
document.body.appendChild(div);
const scaledFontSize = Math.round(
window
.getComputedStyle(div, null)
.getPropertyValue('font-size')
.replace('px', '')
);
document.body.removeChild(div);
const scaleRate = scaledFontSize / 16;
const _sizeType = SizedAndroid[scaleRate];
window.console.log('scaleRate:', scaleRate, _sizeType);
setSizeType(_sizeType);
setScale(scaleRate);
}
}, []);
return [scale, sizeType];
};
export default useFontSize;
export { Sized };
代码里面,ios是通过body标签的-webkit-text-size-adjust属性来获取当前缩放比例的,由于app在切换字体的时候,不会重新渲染页面,也就不会再执行这段获取逻辑,所以需要通过 MutationObserver 来监听body标签的style属性变化,确保同步。
android则是新建了一个test dom,设置基准字体为16px,插入到页面后,通过getComputedStyle获取实际展示的字体小大,然后相除,获取所放的比例。
至此,字体大小类型、缩放比例,都存储到globalContext中,以便其他组件进行使用。
应用1:DOM的排版样式适配
设置字体大小后,页面的排版难免会发生错乱,理想的情况下,是可以一套样式,适配多种字体,降低维护成本。但是有些情况,仍然需要写多套样式来做排版样式适配。
所以,我们可以编写一个这样的组件,可以根据当前的字体类型,自动匹配styles中的classname,在不同字体大小classname下写样式适配代码,如:
import styles from './test.less';
<FontSizeAdapter styles={styles}>
<div classsName={styles.label}>xxxx</div>
</FontSizeAdapter>
test.less内容如下:
.sm,
.nm { // sm 小号字体,nm 正常字体
.label {
.line(2); // 最多展示2行
}
}
.md,
.lg { // md 大号字体, lg 超大号字体
.label {
.ellipsis(); // 单行省略
}
}
FontSizeAdapter的实现方式就是消费globalcontext中字体类型,然后给容器添加一个样式名。
import React, { useContext } from 'react';
import classnames from 'classnames';
import GlobalContext from '@/layouts/GlobalContext';
import { Sized } from '@/layouts/useFontSize';
const defaultStyles = {
[Sized.i]: 'fs-init',
[Sized.sm]: 'fs-small',
[Sized.nm]: 'fs-normal',
[Sized.md]: 'fs-medium',
[Sized.lg]: 'fs-large',
};
const FontSizeAdapter = (props) => {
const { styles = defaultStyles, children } = props;
const globalContext = useContext(GlobalContext);
return (
<div className={classnames('fs-adapter', styles[globalContext.sizeType])}>
{children}
</div>
);
};
export default FontSizeAdapter;
应用2:文案保留原有的大小
部分场景下,有些文案不需要随字体大小类型变化,保留原有的font-size,如前面的文章说的,我们可以缩放回去,来实现,就像该组件。
用法:
<DontScaleFontSize>
我是不需要放大的文案
</DontScaleFontSize>
DontScaleFontSize实现如下,也是一个globalContext的字体缩放比例,以及字体大虾类型的一个消费者:
import React, { useContext, useRef, useState, useEffect } from 'react';
import GlobalContext from '@/layouts/GlobalContext';
const DontScaleFontSize = (props) => {
const { children } = props;
const ref = useRef();
const { scale } = useContext(GlobalContext);
const [fs, setFs] = useState(undefined); // font-size
const [lh, setLH] = useState(undefined); // line-height
useEffect(() => {
if (typeof scale === 'undefined') {
throw new Error('scale not injected in');
}
if (scale === 1) {
return;
}
const scaledFontSize = Math.round(
window
.getComputedStyle(ref.current, null)
.getPropertyValue('font-size')
.replace('px', '')
);
const scaledLineHeight = Math.round(
window
.getComputedStyle(ref.current, null)
.getPropertyValue('line-height')
.replace('px', '')
);
/**
* scaledFontSize / scale,是原字体的大小,放入到页面中,
* 依然会放大,所以需要再除去放大比例
*/
const originFS = Math.round(scaledFontSize / scale / scale);
const originLH = Math.round(scaledLineHeight / scale / scale);
setFs(originFS);
setLH(originLH);
}, [scale]);
const wrapStyle = fs ? { fontSize: `${fs}px`, lineHeight: `${lh}px` } : null;
return (
<span ref={ref} style={wrapStyle}>
{children}
</span>
);
};
export default DontScaleFontSize;
需求
在一个h5的移动端项目中,需要支持设置多种字体大小,实现方案是通过webview来实现的,见文章:移动端字体放大问题的研究。简单来说,iOS中,通过设置body标签style的
-webkit-text-size-adjust值来实现,例如设置为137.5%,则在safari内核中的浏览器展示页面,所有DOM的font-size都会被放大37.5%;而在android中,则是通过android-webview的setTextZoom这个api来实现的。问题
前端如何获取到当前页面设置的字体大小类型?
方式1: 通过原生app
最简单的方式,就是由app的同学告诉我们,当前字体的缩放类型,例如,可以放置在每一个页面的url中,添加一个查询参数,该参数的值是约定的字体类型值,例如:
/home?fs=[sm,nm,md,lg]
其中sm表示小号,nm表示正常,md表示略大,lg表示超大。前端同学就可以通过解析url,来获取当前的字体类型。
又或者可以通过下发事件的方式,当页面被初始化的时候,触发事件,将具体的字体大小类型传给我们,如在前端页面中,我们可以这样监听app下发的事件
document.addEventListener('fontsizeInit',handle)
其中fontsizeInit与app同学约定好的事件名称,handle函数则是具体的取参逻辑等。
方式2: 前端自行判断
在一些第三方的webview中,无法要求他们在告知前端页面的当前字体大小类型,就可以通过前端自行来判断,本质的原理是根据android与iOS实现不同,具体来获取,也是较灵活的一种方式。
思路是,可以提取这部分逻辑成一个公用hook,然后在全局文件中(例如baseLayout.js全局layout中)使用,获取到当前的字体类型,然后注入给需要的组件。
如baseLayout.js:
而具体的useFontSize的代码判断逻辑:
代码里面,ios是通过body标签的-webkit-text-size-adjust属性来获取当前缩放比例的,由于app在切换字体的时候,不会重新渲染页面,也就不会再执行这段获取逻辑,所以需要通过 MutationObserver 来监听body标签的style属性变化,确保同步。
android则是新建了一个test dom,设置基准字体为16px,插入到页面后,通过getComputedStyle获取实际展示的字体小大,然后相除,获取所放的比例。
至此,字体大小类型、缩放比例,都存储到globalContext中,以便其他组件进行使用。
应用1:DOM的排版样式适配
设置字体大小后,页面的排版难免会发生错乱,理想的情况下,是可以一套样式,适配多种字体,降低维护成本。但是有些情况,仍然需要写多套样式来做排版样式适配。
所以,我们可以编写一个这样的组件,可以根据当前的字体类型,自动匹配styles中的classname,在不同字体大小classname下写样式适配代码,如:
test.less内容如下:
FontSizeAdapter的实现方式就是消费globalcontext中字体类型,然后给容器添加一个样式名。
应用2:文案保留原有的大小
部分场景下,有些文案不需要随字体大小类型变化,保留原有的font-size,如前面的文章说的,我们可以缩放回去,来实现,就像该组件。
用法:
DontScaleFontSize实现如下,也是一个globalContext的字体缩放比例,以及字体大虾类型的一个消费者: