- 作者:老汪软件技巧
- 发表时间:2024-12-18 04:03
- 浏览量:
前言
日常开发中,响应式设计我们会经常遇到,尤其在移动端H5开发中尤为明显。通常情况下我们会采用vw 、rem 、 媒体查询等解决方案,通过在html根元素中设置font-size作为基准字体大小,结合px-to-rem或postcss-pxtorem等单位转换工具,可以很快地将px 单位转换为 rem 单位,从而实现响应式设计。
然而,上述方案仅对UI层级,在国际化业务中并不完全适用,缺少文字模块级别的响应式设计。
举个简单的例子:在一个宽度固定的div标签中,当切换不同语言时,能显示完整文案。
针对同一文案,不同语言,它对应的长度是不一样的,如果都用相同的font-size,显然不合适。
想想看,在这种场景下,你该如何实现?
这里有几种方案:
提前看不同语言的文案,针对不同语言用if-else设置不同font-size;使用 JS 实时计算文案的长度,然后等比例地设置font-size,或使用translate缩小dom;使用 SVG 或 Img;使用 Canvas,动态设置文字内容;
上述方案各有利弊,基本都能实现,只是投入产出比的问题。
这时候一个开源的库 Fitty 就大显身手了。
Fitty 是什么?
Fitty 是一个轻量级的 JavaScript 库,可以自动调整文本的字体大小,使其适合父元素的宽度。它特别适合于响应式设计中需要适应大小变化的文本内容。
Fitty 的主要目标是确保文本的字体大小能够适配所指定的容器(元素),让内容在不溢出的情况下可见。
Fitty 核心功能
Fitty 的核心功能包括:
Fitty 实现原理
Fitty 的流程主要分为以下几个步骤:
获取目标元素,根据它查找对应的上下文。
获取目标元素父容器的宽度和当前字体大小,同时还会获取内部文本的内容;
通过创建一个临时的元素,当然也可以是其他的元素,只要能测量内容宽度即可,Fitty 可以测量文本内容在不同字体大小下的宽度;
查找最合适的字体大小,将字体大小从最大到最小范围进行调整,直到找到既能满足容器宽度又不溢出的字体大小;
找到合适的字体大小后,会将其写入目标元素的样式之中;
最后,使用ResizeObserver或window.resize来监视窗口大小和容器文字的变化(如MutationObserver),这使得它能够根据需要实时调整字体大小。
手动实现 Fitty
知道它的内部原理之后,下面就让我们手动实现一个简易版的 Fitty,一起动起手来吧。
1. 获取目标元素
这一步相当简单,一行代码就可以搞定。
function getTarget(target){
return document.querySelector(target);
}
2. 计算父容器宽度
这一步也是相当简单,直接通过parentNode获取父元素,从而通过offsetWidth拿到它的宽度。
需要注意的是,先要有一个基准font,否则就没有对照,无法知道要缩小的比例是多少。基准font可根据实际业务设置,这里为了方便计算,可设置为100。
const parentWidth = element.parentNode.offsetWidth;
let fontSize = 100;
3. 创建 canvas 元素获取内容宽度
先要创建一个额外的元素,注意如果不是canvas,而是其他的标签元素,需要让它隐藏在可视区域之外。若是canvas,可通过ctx.measureText属性拿到它的内容。
function measureTextWidth(text, font) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
4. 查找最合适的字体大小
在 Fitty 中会使用二分查找算法(时间复杂度O(logN))来查找最合适的字体大小。这里简化为从大到小逐步查找,如下:
while (fontSize > minSize) {
const textWidth = measureTextWidth(
element.textContent,
window.getComputedStyle(element).font
);
if (textWidth <= parentWidth) {
break; // 字体大小合适
}
fontSize--; // 减少字体大小
}
5. 写入目标元素样式
找到最合适的字体大小后,直接写入元素之中,如下:
element.style.fontSize = `${fontSize}px`;
6. 监听元素内容
最后需要监听窗口大小的变化,以及文案内容的变更,做到实时更新。
可通过window.resize和MutationObserver实现监听,如下:
// 监听窗口大小变化
window.addEventListener('resize', () => {
console.log('resize');
fitTextToContainer(element, minFontSize, maxFontSize);
});
// 创建一个 MutationObserver 实例
const observer = new MutationObserver(() => {
fitTextToContainer(element, minFontSize, maxFontSize);
});
// 配置 Observer 选项
const config = { childList: true, subtree: true, characterData: true };
// 开始观察目标元素
observer.observe(element, config);
至此,一个简易版的 Fitty 就实现好了,动态响应文案内容的变化并按比例缩小文字。
扩展
然而实际应用之中,我们通常需要动态设置很多其他的内容,比如宽度、最小字体、支持多行等。
1. 支持动态指定宽度
通常这里的宽度,一般是容器的宽度,这里可通过传递参数的方式设置,如下:
function fitty(target, containerWidth) {
//...
// 设置容器宽度为传入的参数值
if (containerWidth) {
container.style.width = containerWidth + 'px';
}
//...
}
2. 支持动态设置文字大小范围
有很多场景,我们需要限定文字大小的范围,比如它的最小值不能小于8,最大值不能超过100,如下:
function fitty(target, containerWidth, minFontSize, maxFontSize) {
//...
let fontSize = maxFontSize;
// 设置容器宽度为传入的参数值
while (fontSize > minFontSize) {
//...
fontSize--; // 减少字体大小
}
//...
}
3. 支持多行伸缩
以上,实现的场景都是针对单行的,然而现实中往往需要显示多行。
要实现多行缩小,首先要找到内容长度最大的那一行,并以最大行宽度作为基准,与容器宽度做对比,进行文字大小的伸缩。
function getMaxLineWidth(element) {
const lines = element.textContent.split(/\\n/);
let maxWidth = 0;
console.log('lines', lines);
for (const line of lines) {
const lineWidth = measureTextWidth(
line,
window.getComputedStyle(element).font
);
if (lineWidth > maxWidth) {
maxWidth = lineWidth; // 更新最大宽度
}
}
return maxWidth;
}
function fitTextToContainer(element, minFontSize, maxFontSize) {
//...
while (fontSize > minFontSize) {
const maxLineWidth = getMaxLineWidth(element); // 获取最大行宽度
if (maxLineWidth <= parentWidth) {
break; // 字体大小合适
}
fontSize--; // 减少字体大小
}
//...
}
function fitty(target, containerWidth, minFontSize, maxFontSize) {
//...
}
// 使用 Fitty,并通过参数传入容器宽度、最小和最大字体大小
fitty('.fittext', 500, 10, 100);
demo预览:stackblitzstartersm9wsab5s-g34h--8080--c8c182a3.local-credentialless.webcontainer.io/page-10.htm…
这里只是做简易的知识布道,代码行数不过100行,当然现实使用中会遇到更多的应用场景,可结合实际业务做更多的扩展和兼容性处理。