想实现一个带圆角边框的文本,如果直接用DOM,那只用设置一个div的border-radius,然后包裹文本即可,例如如下一个简单的例子:
<!-- CSS -->
<style>
.radius {
width: 80px;
height: 40px;
line-height: 40px;
font-size: 16px;
text-align: center;
border: 1px solid black;
border-radius: 20px;
}
</style>
<!-- HTML -->
<div class='radius'>
圆角边框
</div>
但是如果想使用svg实现并非这么简单!需要对<rect>和<text>进行组合。我就简单介绍下自己实现的过程吧。
一、简单介绍 <rect> 和 <text>元素
1.1 <rect>
rect元素是 SVG 的一个基本形状,用来创建矩形,有以下私有属性:
x, y:用于设置矩形左上角位置。width, height: 用于设置矩形的宽高rx, ry: 用于设置圆角矩形的弧度
下面绘制一个简单的圆角矩形
<svg height="200" height="200" viewbox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<rect x='0' y='0' rx="20" ry="20" width="100" height="40" fill="transparent" stroke="black"/>
</svg>
1.2 <text>
text元素定义了一个由文字组成的图形。有以下私有属性:
x, y:文本的位置- x: 由
text-anchor决定文本何处在这个位置 - y: 文本的基线
- x: 由
dx, dy:文本在x和y方向上的偏移量text-anchor:描述该文本与所给点的对齐方式 (开头、中间、末尾对齐)。start: 文本字符串的开始位置即当前文本的初始位置。middle: 文本字符串的中间位置即当前文本的初始位置end: 文本字符串的末尾即当前文本的初始位置
rotate:用于设置文本的旋转角度textLength:用于设置文本所占的宽度,如果大于文本自身宽度,则拉伸文本间隔,如果小于文本自身宽度,则压缩文本。lengthAdjust:当设置了textLength后,该属性控制文本如何拉伸到该textLength属性定义的长度。可设置spacing | spacingAndGlyphs,默认为spacing
<svg height="200" height="200" viewbox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20, L 100 20" stroke="black"/>
<text x="0" y="20" font-size="16px">abcdefj</text>
</svg>
二、SVG 实现带圆角边框的文本
2.1 规定位置
因为想要实现的效果是文本在矩形框居中对齐,所以这里将 (x, y) 规定为矩形边框和文本的中心位置,
那么怎么使得矩形和文本的中心落在(x, y)上呢?
2.2 绘制矩形
如上所述,<rect>的(x, y)属性指的是矩形左上角的位置,那么要想让矩形的中心移动到(x, y)位置,只需要将矩形向左移动宽度的一半,向上移动高度的一半即可。具体可通过transform属性来实现,即:
transform="translate(-width / 2, -height / 2)"
此外,dx, dy的值通常设置为高度的一半。
那么假设中心位置为(x, y),矩形宽高为(w, h),绘制矩形的代码如下
<rect x='x' y='y' rx="h/2" ry="h/2" width="w" height="h" transform="translate(-w/2, -h/2)" fill="transparent" stroke="black"/>
2.3 绘制文本
如上所述,<text> 的(x, y)属性默认指的是文本开头位置,但是设置text-anchor="middle"可以使得文本与(x, y)居中对齐。但这仅是横向居中,对于纵向我们需要使用dy属性向下平移使得文本纵向居中,平移多少,通常设置为font-size/2上下浮动1-2。
绘制文本代码如下:
<text x='x' y='y' dy='3' fill='#757685' fontSize="10" textAnchor='middle'>
那么经过如上几步,我们就可以绘制出一个带圆角边框的文本了:
<svg height="200" height="200" viewbox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<rect x='40' y='40' rx="10" ry="10" width="50" height="20" transform="translate(-25, -10)" fill="transparent" stroke="black"/>
<text x='40' y='40' dy='5' fill='#757685' fontSize="12" text-anchor='middle'>圆角</text>
</svg>
三、圆角边框随文本长度自适应
上述虽然绘制出了带圆角边框的文本,但是所有属性都是静态的,如果文本边长那么就会溢出边框了。那我们如何实现圆角边框随着文本长度自适应呢。
3.1 获取文本长度
既然要自适应文本长度,那么首先肯定要获取文本的长度。原先菜鸟的我以为只需要fontSize * str.length即可,这对中文还行得通,但是对于英文字符,每个字符的宽度是不一样的,那么对于每个字符的宽度计算也是不一样的。
我这里也是借鉴了一位大佬的代码,首先定义英文字符的宽度:
const LetterMap = {
' ': 0.3329986572265625,
a: 0.5589996337890625,
A: 0.6569992065429687,
b: 0.58599853515625,
B: 0.6769989013671875,
c: 0.5469985961914062,
C: 0.7279998779296875,
d: 0.58599853515625,
D: 0.705999755859375,
e: 0.554998779296875,
E: 0.63699951171875,
f: 0.37299957275390627,
F: 0.5769989013671875,
g: 0.5909988403320312,
G: 0.7479995727539063,
h: 0.555999755859375,
H: 0.7199996948242188,
i: 0.255999755859375,
I: 0.23699951171875,
j: 0.26699981689453123,
J: 0.5169998168945312,
k: 0.5289993286132812,
K: 0.6899993896484375,
l: 0.23499908447265624,
L: 0.5879989624023437,
m: 0.854998779296875,
M: 0.8819992065429687,
n: 0.5589996337890625,
N: 0.7189987182617188,
o: 0.58599853515625,
O: 0.7669998168945312,
p: 0.58599853515625,
P: 0.6419998168945312,
q: 0.58599853515625,
Q: 0.7669998168945312,
r: 0.3649993896484375,
R: 0.6759994506835938,
s: 0.504998779296875,
S: 0.6319992065429687,
t: 0.354998779296875,
T: 0.6189987182617187,
u: 0.5599990844726562,
U: 0.7139999389648437,
v: 0.48199920654296874,
V: 0.6389999389648438,
w: 0.754998779296875,
W: 0.929998779296875,
x: 0.5089996337890625,
X: 0.63699951171875,
y: 0.4959991455078125,
Y: 0.66199951171875,
z: 0.48699951171875,
Z: 0.6239990234375,
0: 0.6,
1: 0.40099945068359377,
2: 0.6,
3: 0.6,
4: 0.6,
5: 0.6,
6: 0.6,
7: 0.5469985961914062,
8: 0.6,
9: 0.6,
'[': 0.3329986572265625,
']': 0.3329986572265625,
',': 0.26399993896484375,
'.': 0.26399993896484375,
';': 0.26399993896484375,
':': 0.26399993896484375,
'{': 0.3329986572265625,
'}': 0.3329986572265625,
'\\': 0.5,
'|': 0.19499969482421875,
'=': 0.604998779296875,
'+': 0.604998779296875,
'-': 0.604998779296875,
_: 0.5,
'`': 0.3329986572265625,
' ~': 0.8329986572265625,
'!': 0.3329986572265625,
'@': 0.8579986572265625,
'#': 0.6,
$: 0.6,
'%': 0.9699996948242188,
'^': 0.517999267578125,
'&': 0.7259994506835937,
'*': 0.505999755859375,
'(': 0.3329986572265625,
')': 0.3329986572265625,
'<': 0.604998779296875,
'>': 0.604998779296875,
'/': 0.5,
'?': 0.53699951171875,
'"': 0.33699951171875
};
然后根据上述定义的字符宽度来计算整个文本的长度:
// 计算非中文字符宽度
const getLetterWidth = (letter, fontSize) => fontSize * (LetterMap[letter] || 1);
// 计算文本宽度
const getTextWidth = (text, fontSize) => {
// 中文匹配正则
const pattern = new RegExp('[\u4E00-\u9FA5]+');
// 文本宽度
const textWidth = text.split('').reduce((pre, curLetter) => {
// 单个字符宽度
const letterWidth = pattern.test(curLetter) ? fontSize : getLetterWidth(curLetter, fontSize);
return pre + letterWidth;
}, 0);
return textWidth;
};
3.2 确定矩形大小
确定了文本的字体大小(font-size)和文本宽度后,我们就可以确定文本外部圆角边框的大小了。
为了防止边框和文本会重合,边框的大小要稍微大于文本的大小,即:
- 圆角矩形宽度:文本宽度 + x
- 圆角矩形高度:文本字体大小 + y
至于 x , y 的值,可自行按个人喜好设定,但是个人建议:x>=12, y>=10
我这里对于 x, y 值得设定如下:
- x = font-size + 12
- y = 12
3.3 绘制
假设我们要在某个SVG元素内部
- (200, 200) 的位置
- font-size=14
- 字体颜色 #fff
- 矩形背景色:#9b95c9
- 矩形边框颜色:#C5C5D1
的带圆角边框文本 “圆角文本”
- 首先我们定义一个创建svg元素的函数,与创建dom元素稍微不同,需要使用
document.createElementNS()函数:
const createSvgChildElement = (tagName) => {
return document.createElementNS('http://www.w3.org/2000/svg', tagName);
}
- 将绘制圆角文本封装成一个函数,如下:
const drawRadiusText = (svgEl, x, y, text, fontsize, color, backgroundColor, borderColor) => {
// 创建 rect 和 text 元素
const rectEl = createSvgChildElement('rect');
const textEl = createSvgChildElement('text');
// 设置rect元素属性
rectEl.setAttribute('x', x);
rectEl.setAttribute('y', y);
rectEl.setAttribute('width', getTextWidth(text, fontsize) + fontsize + 12);
rectEl.setAttribute('height', fontsize + 12);
rectEl.setAttribute('rx', rectEl.getAttribute('height') / 2)
rectEl.setAttribute('ry', rectEl.getAttribute('height') / 2)
rectEl.setAttribute('fill', backgroundColor);
rectEl.setAttribute('stroke', borderColor);
rectEl.setAttribute('transform', `translate(-${rectEl.getAttribute('width') / 2}, -${rectEl.getAttribute('height')/ 2})`);
// 设置text元素属性
textEl.setAttribute('x', x);
textEl.setAttribute('y', y);
textEl.setAttribute('dy', fontsize / 2 - 2);
textEl.setAttribute('fontSize', fontsize);
textEl.setAttribute('fill', color);
textEl.setAttribute('text-anchor', 'middle');
textEl.innerHTML = text;
// 向svg中添加rect和text子元素
// 注意:rect一定要在text之前添加,矩形有背景色时,会遮挡文本
svgEl.appendChild(rectEl);
svgEl.appendChild(textEl);
}
注意:rect一定要在text之前添加,矩形有背景色时,会遮挡文本
- 获取svg元素,并在其中指定位置绘制
const svgEl = document.querySelector('svg');
drawRadiusText(svgEl, 200, 200, "圆角文本", 14, '#fff', '#9b95c9', '#C5C5D1');
绘制效果如下。外面黑色边框是svg元素的大小,即400x400,圆角文本刚好绘制在中心(200, 200)处:
SVG元素HTML代码如下:
<svg width="400" height="400" version="1.1" xmlns="http://www.w3.org/2000/svg">
</svg>
到此绘制一个随文本宽度动态改变的带圆角边框文本就完成了。
四、React 中实现
目前写原生JS已经很少了,React 或 Vue 来编写前端页面的,那么在React中如何实现呢。其实区别不大,就是在useEffect 或者 componentDidAmount中获取带圆角边框文本的配置(config),然后动态渲染即可。
由于在渲染页面结构时,js还未加载,为了防止一些不必要的警告或错误(例如:config为null),所以我们等获取到config后再渲染。代码如下:
<svg width="400" height="400" viewBox="0 0 400 400" version="1.1" xmlns="http://www.w3.org/2000/svg">
{config && drawRadiusText(config)}
</svg>
完整代码如下(其中获取文本宽度函数参考上文):
import React, {useState, useEffect} from "react";
export default function RadiusText() {
const [config, setConfig] = useState(null); // 圆角文本属性
useEffect(() => {
setConfig({
x: 200,
y: 200,
text: '圆角文本',
fontSize: 14,
color: '#fff',
backgroundColor: '#9b95c9',
borderColor: '#C5C5D1'
})
}, [])
const drawRadiusText = (config) => {
const {x, y, text, fontSize, color, backgroundColor, borderColor} = config;
const rectWidth = getTextWidth(text, fontSize) + fontSize + 12;
const rectHeight = fontSize + 12;
console.log(rectWidth, rectHeight)
const rectProps = {
x,
y,
width: rectWidth,
height: rectHeight,
rx: rectHeight / 2,
ry: rectHeight / 2,
fill: backgroundColor,
stroke: borderColor,
transform: `translate(-${rectWidth / 2}, -${rectHeight / 2})`
};
const textProps = {
x,
y,
dy: fontSize / 2 - 2,
fontSize,
fill: color,
textAnchor: 'middle'
};
return (
<g>
<rect {...rectProps} />
<text {...textProps}>{text}</text>
</g>
);
}
return (
<div className="radius-text">
<svg width="400" height="400" viewBox="0 0 400 400" version="1.1" xmlns="http://www.w3.org/2000/svg">
{config && drawRadiusText(config)}
</svg>
</div>
)
}
本文到这里就结束了,是自己对于使用svg绘制带圆角边框文本的一些思考。当然代码中还有很多能够改进的地方,希望大佬们提出指正。