我们在做国际化网站的时候,必然会遇到多语言的场景,而同一个词语或者语句,翻译成不同国家的语言后,其长度是不一样的。
假设有这么一个场景:有一个固定宽度的按钮,按钮文案为中文的时候,它可以一行展示,但是翻译成英文或者其它语言的时候,长度超过了按钮的宽度,默认情况下是会换行展示成两行。现在需求是当文案超过按钮宽度之后,缩小文案,让文案始终展示成一行。
分析上述需求,我们能获得实现需求的两个关键点:
- 如何判断文案是否超过按钮的宽度?
- 如何缩小文案,让文案展示成一行?
判断文案是否超过按钮的宽度
对于这个要求,我们脑海里第一个浮现的想法可能是:通过文案的长度来判断其是否超出了按钮,但是这样的做法好像不太行,因为文案的长度和文案的宽度不挂钩,对于同一长度的文案,其在页面上渲染的宽度是不一样的,比如 iii 和 GGG,它们的长度虽然都是 3 ,但是显然 GGG 的宽度比 iii 的大。
想要正确地测量出文案的宽度,这时候我们就需要用到 canvas 画布了,我们知道 canvas 除了可以被用来绘制图形,它也可以用来绘制文本,更重要的是,它提供了测量文本的方法:measureText()
我们来测量下 iii 和 GGG 的宽度,代码如下:
<body>
<canvas id="canvas" width="200" height="100" style="border:1px solid #000000;"></canvas>
</body>
<script>
const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
// 移动原点到画布中央
context.translate(canvas.width/2,canvas.height/2)
// 绘制文本 iii 和 GGG
context.fillText('iii',0,0)
context.fillText('GGG',0,20)
// 测量文本 iii 和 GGG 的宽度
const iText = context.measureText('iii')
const GText = context.measureText('GGG')
console.log(iText.width,GText.width)
</script>
上图中,我们在控制台输出了 iii 和 GGG 的宽度,显然, GGG 的宽度要大得多。
缩小文案,让文案展示成一行
我们想让内容展示成一行,可以设置如下 css 样式:
white-space: nowrap;
缩小字体,我们第一个想到可能就是直接设置 font-size,但是浏览器是有最小字体设置的,如果小于某个数值,就不会再生效。比如 Chrome 浏览器默认的最小字体为 12px,当我们设置小于 12px 的值,最终还是展示 12px 大小。当 12px 的文案还是超出了按钮宽度的时候,这个方法就失效了。
想要设置小于 12px 的字体,可以使用 transform 的 scale 来缩放,比如如下样式将元素缩小为原来的 60%:
transform: scale(0.6);
实现需求
经过上述分析之后,我们举个例子来实现需求,如下:
上图中,当文案翻译成英文的时候,英文内容过长,默认情况下换行展示成了两行,而需求想要的结果如下图:
示例代码如下:
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.button {
background-color: #4CAF50;
border: none;
color: #fff;
margin-bottom: 20px;
text-decoration: none;
display: inline-block;
cursor: pointer;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
width: 200px; // 按钮的宽度固定为 200
height: 50px;
white-space: nowrap; // 不换行
font-size: 16px;
font-family: sans-serif;
}
</style>
<body>
<div class="container">
<button class="button">
<span id="text">这是一个国际化翻译文案</span>
</button>
<button onclick="handleTranslationChange()">切换成英文</button>
</div>
</body>
<script>
let buttonWidth = 200 // 按钮的固定宽度
let handleTranslationChange = () => {
const text = document.getElementById('text')
// 文案内容改为英文
text.innerText = 'This is an international translation copy'
// 创建 canvas 元素
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// font 属性值默认是 10px sans-serif
context.font = "16px sans-serif"
// 调用 measureText 方法测量翻译后的
const measureText = context.measureText(text.innerText)
// 如果翻译之后文案宽度大于按钮,则缩小文案
if (measureText.width > buttonWidth) {
text.style.transform = 'scale(0.6)'
}
}
</script>
上述 css 代码中,我们设置了按钮的样式,固定其宽度为 200px,同时设置它里面的文案不换行,字体大小为 16px。html 代码中,我们添加一个操作按钮,点击它触发 handleTranslationChange 事件,把中文翻译成英文。在handleTranslationChange 方法中,我们手动把按钮的 innerText 设置成对应的英文,模拟翻译。然后创建 canvas 元素,调用其 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。
关键代码:context.font = “16px sans-serif”(默认情况下 context.font = “10px sans-serif”),即 font 要设置成和按钮文案一样,如果不设置,那么后面调用 measureText() 方法的测量宽度的时候,将会以默认值 10px的大小来测量,这样的测出的宽度实际是小于真实的文案的宽度的。
上述代码最终效果如下:
翻译前:
点击翻译后:
最后
上述实例代码中,我们是手动改变 button 的翻译的,但在实际的业务场景中,我们并不知道用户会选择什么语言,我们无法确定按钮最终的文案是什么,所以为了测量文案的宽度,我们需要使用 MutationObserver 来观察文本节点的变化,只要节点发生变化,我们就进行测量,从未决定是否缩小文案,代码调整如下:
let buttonWidth = 200
let handleTranslationChange = () => {
const text = document.getElementById('text')
text.innerText = 'This is an international translation copy'
}
const text = document.getElementById('text')
const observer = new MutationObserver(entries => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// font 属性值默认是 10px sans-serif
context.font = "16px sans-serif"
const measureText = context.measureText(text.innerText)
// 如果翻译之后文案宽度大于按钮
if (measureText.width > buttonWidth) {
text.style.transform = 'scale(0.6)'
}
})
observer.observe(text,{ childList: true, subtree: true })
在上述代码中,handleTranslationChange() 方法要做的只是模拟用户切换语言的行为,然后其原来测量文案宽度的相关逻辑移动到 MutationObserver 的回调函数中。