(建议收藏)React项目中文本宽度自适应组件的实现过程

举报
小黑豆豆 发表于 2022/07/27 14:59:24 2022/07/27
【摘要】 还在为文本宽度自适应需求发愁么?赶紧点进来看看文本宽度自适应标签组件的开发思路和实现过程吧,看完之后你便会豁然开朗,怡然自乐!!!

前言

这次就来还上一篇文章留下的债,那就是详细地讲解react-text-width-auto这个组件是如何实现的?这里可能有疑问,为什么上一篇文章提到的组件名是text-width-auto-label,这里就变成了react-text-width-auto了呢?在写这篇文章时,我觉得上一篇文章定好的名字不是那么匹配组件,想将其改为react-text-width-auto,但是发现text-width-auto-label这个包已经被多人下载使用,这时如果强行将包名改为react-text-width-auto,那需要将之前的删除再重新上传,为了不影响下载使用的人,我选择重新生成一个包发布上去。所以小伙伴们可以在npm上看到这两个组件包,之前下载使用text-width-auto-label的小伙伴也可以继续使用,我不会将包删掉的,后续的小伙伴若要使用就请下载react-text-width-auto包进行使用吧。

背景

老规矩,这是一个实战组件的封装,那肯定有其背景的。那就是我们在开发CAD嵌入页面时,另一个页面使用多行文本输入的内容有点多,可能会有3行左右的文字,而该页面的内容区域稍小且不想以多行文本方式进行展示,所以需求要求:文字的大小跟随文字的多少来自动变小且一行展示完所有文本。如下图所示:

img0.png

上图展示的是已经开发完成的效果图,最终完美满足需求:一行展示完所有的文字

思考

如果这个需求拿给你,你会怎么去做?想必很多人一开始拿到这个需求就是往动态的改变字体大小方向去思考的,包括我自己,一开始也是打算这样去做的,但是到后面你会发现,如果动态的去改变字体大小,在某些浏览器上字体支持的最小字号也就是10px,如果小于10px字号的需要使用缩放,那又怎么去进行缩放呢?是不是越想越觉得不好实现?

其实思考问题的方向有很多,当动态改变字体大小不好实现,那就从其他方向思考。怎么能让这些超过容器宽度的文本能单行显示完?那就是把这些文字给绘制出来,绘制就要使用到canvascanvasHTML5提供的新元素标签,它可以用于图形的绘制,说到底它就是画布,可以绘制路径、盒、圆、字符以及添加图像等。

我们可以通过canvas把容器的宽度限制死,然后在既定的宽度中去把这些文本绘制到容器中即可实现上述需求。思路就是这么一个思路,具体的操作还得继续往下瞧→

开发

这里聊的技术栈为react,组件也是在react项目使用的,前言里面提到发布的包也是适用于react项目中。至于其他技术栈的小伙伴,请参考实现思路自行开发即可。

下面就来介绍这个组件的代码是如何实现的,那就是:通过获取父级节点的字体样式和容器的宽高,来设置canvas的宽高,并且将父级的字体样式设置给我canvas的字体,然后通过计算使文本在容器中居中显示即可。

1.1 获取父级节点

parentNode属性可返回某节点的父节点,如果指定的节点没有父节点则返回null

import * as React from 'react';
import { useEffect, useRef } from 'react';

interface WidthAutoLabelProps {
    children: string;  // 要绘制的文本
}


/**
 * 文本宽度自适应标签组件
 * @param props
 * @constructor
 */
const WidthAutoLabel = (props: WidthAutoLabelProps) => {
    const { children = '' } = props;
    const ref = useRef<any>(null);

    useEffect(() => {
        drawText();
    }, [children])


    const drawText = () => {
        let canvas = ref.current;
        if (!canvas) {
            return;
        }
        let parentNode = canvas.parentNode; // 获取父级节点
        if (!parentNode) {
            return;
        }
    }

    return (
        <canvas ref={ref}></canvas>
    )
}

export default WidthAutoLabel;

1.2 获取父级样式

获取样式有两个api可以获取,分别是getComputedStyleelement.style。这个组件中是通过前者来获取父级节点的样式结果的,如下:

const { fontSize, fontStyle, fontWeight, fontFamily, color } = getComputedStyle(parentNode, null);

那么为什么不使用element.style这种方式呢?虽然二者返回的都是CSSStyleDeclaration对象,但是二者有以下两个区别:

  • element.style读取的只是元素的内联样式,即写在元素的style属性上的样式;而getComputedStyle读取的样式是最终样式,包括了内联样式嵌入样式外部样式

  • element.style既支持读也支持写,通过element.style可以改写元素的样式。而getComputedStyle仅支持读并不支持修改。所以可以通过使用getComputedStyle读取样式,通过element.style修改样式。

而这里仅需获得父级节点的最终样式,而不去对父级节点做修改样式的操作,所以getComputedStyle更符合场景需求。通过getComputedStyle我们能获得元素多达335种的样式,如下图:

img1.png

好了,既然父级节点的样式获取成功,接着再去获取父级的宽高并给canvas设置同样的宽高。如下:

const { clientWidth, clientHeight } = parentNode;
canvas.width = clientWidth;
canvas.height = clientHeight;

其实父级节点的宽高通过getComputedStyle也是能获取的,只是获取出来的是带了px单位的,如果使用还要再去处理,所以没有采用getComputedStyle获取父级节点的宽高。

img3.png

1.3 设置文本样式

接着就是将获取到的父级样式赋值给canvas的文本样式,以及设置绘制文本时的当前文本基线。

let ctx = canvas.getContext("2d");
// 设置文本样式
ctx.font = `${fontStyle} ${fontWeight} ${fontSize} ${fontFamily}`;
ctx.textBaseline = 'top';

textBaseline属性设置或返回在绘制文本时的当前文本基线。下面的图示演示了textBaseline属性支持的各种基线:

image.png

这里设置的是top,是为了后面通过计算绘制文本的y坐标位置。

1.4 绘制文本

接下来就是要去绘制文本了,绘制文本想的是让它在容器内部居中显示,那就是水平垂直方向都是居中的,所以需要做一些计算,如下:

// 居中显示
let y = Math.ceil((clientHeight - Number(fontSize.replace('px', ''))) / 2);
let x = 0;
const { width: textWidth } = ctx.measureText(children);
if (textWidth < clientWidth) {
    x = (clientWidth - textWidth) / 2;
}

可以看到在水平方向上,获取文本宽度是通过measureText()方法, 它是返回一个对象,该对象包含以像素计的指定字体宽度。这样通过获取文本在向画布输出之前,获取文本的宽度和容器宽度比较,如果文本宽度没有充满容器宽度时,就让它水平居中显示,否则就从水平方向原点开始绘制。

而垂直方向上则是通过获取的容器高度来减去文本字号并除以2后向上取整使得其在垂直方向上始终居中显示。

计算完成之后就可以通过fillText()方法来绘制文本了。由于canvasfillStyle属性默认值为#000,会使得绘制的文本在是黑色背景的界面上看不到文本,所以为了避免这种情况的发生,可以获取父级元素设置字体颜色来解决,然后拿到color元素之后再来通过fillStyle来设置即可。如下:

// 文本颜色
ctx.fillStyle = color;
// 绘制文本
ctx.fillText(children, x, y, clientWidth);

通过以上步骤之后,就可以将上述代码组合得到该组件的完整代码,请往下看→

1.5 完整代码

import * as React from 'react';
import { useEffect, useRef } from 'react';

interface WidthAutoLabelProps {
    children: string;  // 要绘制的文本
}


/**
 * 文本宽度自适应标签组件
 * @param props
 * @constructor
 */
const WidthAutoLabel = (props: WidthAutoLabelProps) => {
    const { children = '' } = props;
    const ref = useRef<any>(null);

    useEffect(() => {
        drawText();
    }, [children]);


    // 绘制
    const drawText = () => {
        let canvas = ref.current;
        if (!canvas) {
            return;
        }
        let parentNode = canvas.parentNode;
        if (!parentNode) {
            return;
        }
        const { fontSize, fontStyle, fontWeight, fontFamily, color } = getComputedStyle(parentNode, null);
        const { clientWidth, clientHeight } = parentNode;
        canvas.width = clientWidth;
        canvas.height = clientHeight;
        let ctx = canvas.getContext("2d");
        // 设置文本样式
        ctx.font = `${fontStyle} ${fontWeight} ${fontSize} ${fontFamily}`;
        ctx.textBaseline = 'top';
        let y = Math.ceil((clientHeight - Number(fontSize.replace('px', ''))) / 2);
        let x = 0;
        // 居中显示
        const { width: textWidth } = ctx.measureText(children);
        if (textWidth < clientWidth) {
            x = (clientWidth - textWidth) / 2;
        }
        // 文本颜色
        ctx.fillStyle = color;
        // 绘制文本
        ctx.fillText(children, x, y, clientWidth);
    }

    return (
        <canvas ref={ref}></canvas>
    )
}

export default WidthAutoLabel;

以上就是该组件的完整代码,从上面代码可以看到,该组件的children接收的是个字符串即需要绘制的文本内容。所以小伙伴们在使用的时候,注意不要传入一个node节点哦。还有就是有人可能会问,为什么不通过给组件设置参数从而不去主动获取父级节点的宽高等样式?其实那样也是可以的,但是那样会使组件显得繁复。现在这样,组件就只关心绘制文本功能,组件就只管自己的事(即逻辑本身),而至于样式问题就交个父级节点去做就好。

1.6 安装使用

组件写好,那就来使用它。前言里面提到,该组件已经发布到npm上了,所以小伙伴们可以到npm上查看并下载,也可以通过如下命令进行安装使用:

npm i react-text-width-auto 或 yarn add react-text-width-auto

安装好之后,在项目中引用使用:

// 引入组件
import WidthAutoLabel from 'react-text-width-auto/src/index';

// 使用
<div
    style={{
        width: 100,
        height: 32,
        margin: '10px auto 0',
        fontSize: '14px',
        fontWeight: 'bold',
        border: '1px solid #f00',
        color: "#fff"
    }}
>
    <WidthAutoLabel>测试文本宽度自适应标签组件</WidthAutoLabel>
</div>

最后实现效果,如下图:

image.png

如果你有需求需要用到该组件但不想去下载安装,那你就直接把这个完整代码拷到你的项目中去,直接创建文件引用即可。(最好还是下载使用吧,这样对作者有一定的支持,作者也会挺开心的^_^)

最后,文本宽度自适应标签组件的开发思路和实现就介绍完了,其实这样的需求还是挺常见的,如果你以后遇到这样的需求就没有必要担心忧伤了,点进来看看你就会豁然开朗,怡然自乐了💓

资源

npm地址:react-text-width-autotext-width-auto-label

github仓库:https://github.com/Jacky010/react-text-width-auto

后语

伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注再走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。