一、实现目标
动态计算字体大小,解决文字超出元素问题:
1、图一为默认字体大小99px,已超出元素宽度
2、图二为动态计算字体大小,使元素刚好可以放下文字
二、准备工作(网上查资料!😚会抄代码也很重要)
1、如何计算文本的宽度?
/**
* 计算文本的宽度
* @param {*} str 字符串
* @param {*} font 所有字体属性 例:font:bold 15px arial,sans-serif;
*/
function getStringWidth(str, font) {
const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
context.font = font
return context.measureText(str).width
}
2、如何最快找到合适的字体大小(二分法)
二分查找算法:二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
三、具体实现
1、编写代码(直接复制代码吧,看第2点,😚!)
class fitFontText {
/**
* @param {*} dom DOM元素
* @param {*} option 配置 {max: 字体最大限制, min: 字体最小限制}
*/
constructor(dom, option = {}) {
this.dom = dom
this.option = option
this.context = document.createElement("canvas").getContext("2d")
}
/**
* 计算文本的宽度
* @param {*} str 字符串
* @param {*} font 所有字体属性 例:font:bold 15px arial,sans-serif;
*/
getStringWidth(str, font) {
this.context.font = font
return this.context.measureText(str).width
}
/**
* @param {*} str 字符串
* @param {*} range 计算范围
* @param {*} style 父元素样式 fw 字重 ff 字体样式 w 元素宽度
* @param {*} first 首次计算
*/
recursGetSize(str, { max = 1, min = 1 }, { fw = "normal", ff = "Arial,sans-serif", w = 0 }, first = true) {
/* 最大值/最小值不允许一样 */
if (min === max) return min
/* 首次计算, 直接计算最大字体, 是否超出限制, 不超出直接采用最大值 */
if (first) {
const tw = Math.round(this.getStringWidth(str, `${fw} ${max}px ${ff}`))
if (w >= tw || max <= 1) return max
}
/* 中间值 */
const mid = Math.ceil((max + min) / 2)
/* 最大与最小值中间隔的数值的个数,是奇还是偶(true偶false奇) */
const even = (max - min - 1) % 2 === 0
/* 计算取中间值(mid)时字符串的宽度 */
const tw = Math.round(this.getStringWidth(str, `${fw} ${mid}px ${ff}`))
/* 二分数值区间(interval) (奇数/偶数有不同区间)*/
const interval = {
less: [min, mid - 1],
more: [even ? mid : mid + 1, max]
}
if (tw > w) {
/* 字符串宽度大于最大限制 */
/* (2)只剩一个值,无法继续二分,获得(最终结果) */
if (interval.less[0] === interval.less[1]) return interval.less[1]
/* 递归查找 */
return this.recursGetSize(str, { max: interval.less[1], min: interval.less[0] }, { fw, ff, w }, false)
} else if (tw < w) {
/* 字符串宽度小于最大限制 */
/* (3)只剩一个值,无法继续二分,需要判断采用最终值的字符串宽度是否超过最大限制(最终结果) */
if (interval.more[0] === interval.more[1]) {
const tw2 = Math.round(this.getStringWidth(str, `${fw} ${interval.more[0]}px ${ff}`))
if (tw2 > w) {
/* (3-1)(最终结果) */
return mid
} else {
/* (3-2)(最终结果) */
return interval.more[0]
}
}
/* 递归查找 */
return this.recursGetSize(str, { max: interval.more[1], min: interval.more[0] }, { fw, ff, w }, false)
}
/* (1)取中间值(mid), 字符串宽度等于最大限制(最终结果) */
return mid
}
size(dom) {
if (dom) this.dom = dom
const { width, fontSize, fontFamily, fontWeight } = getComputedStyle(this.dom)
const widthInt = parseInt(width)
const fontSizeInt = parseInt(fontSize)
const text = this.dom.innerText
const maxSize = this.recursGetSize(
text,
{
max: this.option.max ? this.option.max : fontSizeInt,
min: this.option.min ? this.option.min : 1
},
{ fw: fontWeight, ff: fontFamily, w: widthInt }
)
return maxSize
}
}
2、使用方法
(1)基础用法
const dom = document.getElementById("test")
dom.style.fontSize = new fitFontText(dom).size() + 'px'
(2) 限制字体大小 (max 最大 min 最小) 单位px
const dom = document.getElementById("test")
dom.style.fontSize = new fitFontText(dom, {max: 20, min: 16}).size() + 'px'
3、vue中的使用
在vue中用ref去获取dom后,设置font-size的方法实在太麻烦了(不会动态计算🤣)
解决方案: 可以使用自定义指令解决
const app = createApp({})
// 使 v-fit-font 在所有组件中都可用
app.directive('fit-font', (el) => {
el.style.fontSize = `${new fitFontText(el).size()}px`
})
注册好后,就可以使用了
<div v-fit-font class="text">
发布文章333
</div>