• 作者:老汪软件技巧
  • 发表时间: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行,当然现实使用中会遇到更多的应用场景,可结合实际业务做更多的扩展和兼容性处理。


上一条查看详情 +React封装自定义 Hook 捕获所有错误
下一条 查看详情 +没有了