(建议收藏)React项目中文本宽度自适应组件的实现过程
前言
这次就来还上一篇文章留下的债,那就是详细地讲解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
行左右的文字,而该页面的内容区域稍小且不想以多行文本方式进行展示,所以需求要求:文字的大小跟随文字的多少来自动变小且一行展示完所有文本。如下图所示:
上图展示的是已经开发完成的效果图,最终完美满足需求:一行展示完所有的文字。
思考
如果这个需求拿给你,你会怎么去做?想必很多人一开始拿到这个需求就是往动态的改变字体大小方向去思考的,包括我自己,一开始也是打算这样去做的,但是到后面你会发现,如果动态的去改变字体大小,在某些浏览器上字体支持的最小字号也就是10px
,如果小于10px
字号的需要使用缩放,那又怎么去进行缩放呢?是不是越想越觉得不好实现?
其实思考问题的方向有很多,当动态改变字体大小不好实现,那就从其他方向思考。怎么能让这些超过容器宽度的文本能单行显示完?那就是把这些文字给绘制出来,绘制就要使用到canvas
。canvas
是HTML5
提供的新元素标签,它可以用于图形的绘制,说到底它就是画布,可以绘制路径、盒、圆、字符以及添加图像等。
我们可以通过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
可以获取,分别是getComputedStyle
和element.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
种的样式,如下图:
好了,既然父级节点的样式获取成功,接着再去获取父级的宽高并给canvas
设置同样的宽高。如下:
const { clientWidth, clientHeight } = parentNode;
canvas.width = clientWidth;
canvas.height = clientHeight;
其实父级节点的宽高通过getComputedStyle
也是能获取的,只是获取出来的是带了px
单位的,如果使用还要再去处理,所以没有采用getComputedStyle
获取父级节点的宽高。
1.3 设置文本样式
接着就是将获取到的父级样式赋值给canvas
的文本样式,以及设置绘制文本时的当前文本基线。
let ctx = canvas.getContext("2d");
// 设置文本样式
ctx.font = `${fontStyle} ${fontWeight} ${fontSize} ${fontFamily}`;
ctx.textBaseline = 'top';
textBaseline
属性设置或返回在绘制文本时的当前文本基线。下面的图示演示了textBaseline
属性支持的各种基线:
这里设置的是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()
方法来绘制文本了。由于canvas
的fillStyle
属性默认值为#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>
最后实现效果,如下图:
如果你有需求需要用到该组件但不想去下载安装,那你就直接把这个完整代码拷到你的项目中去,直接创建文件引用即可。(最好还是下载使用吧,这样对作者有一定的支持,作者也会挺开心的^_^)
最后,文本宽度自适应标签组件的开发思路和实现就介绍完了,其实这样的需求还是挺常见的,如果你以后遇到这样的需求就没有必要担心忧伤了,点进来看看你就会豁然开朗,怡然自乐了💓
资源
npm地址:react-text-width-auto、text-width-auto-label
github仓库:https://github.com/Jacky010/react-text-width-auto
后语
伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注再走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。
- 点赞
- 收藏
- 关注作者
评论(0)