对于单行文本,想要自适应容器改变字体大小比较简单,通过容器width和文本字数即可计算出字体大小
本文主要讲述多行文本自适应容器的方法(同样适用于单行的情况),多了一个维度,会稍微复杂一点
前言
- 需要了解数学不等式的运算
- 需要了解一元二次方程的求根公式
- 本文不考虑字体间距以及行高,假定字体间距为0,行高为1,如需考虑,则在字体大小fs上加上相应的数值
预设变量
- 待求解:字体大小
fs
- 已知量:容器宽度
W
、容器高度H
、文字字数NUM
- 未知量:当文本以最佳方式适应容器时,行数:
row
、列数:col
计算字体最大值最小值的步骤
-
根据行数、字体大小与容器高度之前的关系可以得到不等式:
H - fs <= row * fs <= H
-
根据列数、字体大小与容器宽度之前的关系可以得到不等式:
W - fs <= col * fs <= W
-
根据行高、列高、与文本字数之间的关系可得到不等式:
row * col >= NUM
-
将三个不等式消除未知量(步骤1、2的
row * fs <= H
和col * fs <= W
相乘再结合步骤3抵消掉row
和col
)可以很容易得到:fs * fs * NUM <= H * W
,解不等式可以得到fs
的 最大值,该值的极限情况是文本正好完全充满整个容器,实际上这种情况几乎是不存在的,因为很难保证每一行或者每一列正好容纳多少个字,会留有小于一个字大小的空隙。因此fs
的最优值不能取这个最大值,会导致文本溢出容器 -
下面介绍如何获得
fs
的最小值。- 利用步骤1、2的
H - fs <= row * fs
和W - fs <= col * fs
,相乘,可得到(H - fs) * (W - fs) <= row * col * fs * fs
- 步骤3处理可得
fs * fs * NUM <= row * col * fs * fs
- 这两个不等式右边一致,我们比较左边,可以这么理解,一个面积为
H * W
的矩形,划分成一个个面积为fs * fs
的正方形(实际肯定会留有缝隙,但是不影响这里作比较,假定可以正好划分),(H - fs) * (W - fs)
表示在原矩形基础上少了R + L - 1
(R表示一行,L表示一列)的正方形,而fs * fs * NUM
表示有NUM
个正方形,这个NUM
遵循文本以最佳方式适应容器的原则,这么多个正方形必定会填充至容器的最后一行,也就是在原矩形基础上少了最多R
(R表示一行)正方形 - 因此,得到
(H - fs) * (W - fs) <= fs * fs * NUM
,通过一元二次方程的求根公式可得fs
的 最小值 - 该值的极限情况是文本留有一行空隙填充容器,因此不会发生溢出,但是也无法满足以最佳方式适应容器(正好填充),
fs
的 最优解在最大最小值之间。
- 利用步骤1、2的
推出最优解
- 有两种方法,一种是从最大值出发,校验高度上是否溢出,溢出则减去固定值(设置为1即可)后再做校验,直到不溢出为止,得到的就是最优解
- 另一种是从最小值出发,校验高度上是否溢出,不溢出则加1后再做校验,直到溢出为止,得到最优解
- 因为分析可知最优解更靠近最小值,所以从最小值出发可减少循环次数
- 校验方法:从最小值出发,
Math.ceil(NUM / Math.floor(W / fs)) * fs > H
满足该条件则溢出了,中止循环,否则fs + 1
继续循环,循环结束后fs - 1
为最优解 - 得到最优解后可以设置期望字体大小的最大值和最小值
参考代码
autoModifyFontSize(elList){
elList.forEach(el => {
const NUM = el.innerText.length;
const W = el.offsetWidth;
const H = el.offsetHeight;
const minFontSize = 28;
const maxFontSize = 84;
// 边界最大最小值, 三个不等式 H - fs <= row * fs <= H W - fs <= column * fs <= W row * column >= NUM(当成等于处理)
// let fontSizeMax = Math.sqrt(W * H / NUM);
// console.log('Max', fontSizeMax)
let fontSizeMin = (Math.sqrt((W + H) * (W + H) + 4 * W * H * NUM - 4 * W * H) - W - H) / (2 * NUM - 2)
console.log('Min', fontSizeMin)
let signState = true;
let fontSize = fontSizeMin;
while (signState) {
if(Math.ceil(NUM / Math.floor(W / fontSize)) * fontSize > H){
// 中止
signState = false;
break;
}else{
// 继续
signState = true;
}
fontSize = fontSize + 1;
}
fontSize = fontSize - 1;
if(fontSize >= maxFontSize){
fontSize = maxFontSize
}
if(fontSize <= minFontSize){
fontSize = minFontSize
}
console.log(fontSize)
el.style.fontSize = `${fontSize}px`;
})
},