当前位置: 首页 > Web前端 > HTML5

JavaScript实现文本溢出自动将字体缩小到N行以适应容器

时间:2023-04-05 01:17:07 HTML5

序列。当一个页面需要在不同语言之间切换时,很难在不同语言中保持相同的文本长度。有的时候,文本会无限显示wrapping影响整个页面的布局,所以需要通过一些方法来保证文本容器的布局在不同语言中保持不变,也就是限制显示的文本在N以内行,保证容器不会被拉伸或溢出容器,尽可能在有限的空间内展示更多的文字。(设计稿给出最多一行文字)(实际页面在英文文案下变成两行)如何限制文字显示在N行,通常采用以下方法:文字超过N行并且滚动条文字超过N行显示超过N行的省略号文字,将字号设置小一点,如果仍然超过,则显示超过N行的省略号文字,缩小字号至刚好覆盖容器,缩小设置为最小字号,如果还是超过,先显示省略号,这两种方法可以通过设置html和css来实现,比较简单。文章主要讨论第三种和第四种方法的可行性和实现过程。为了操作方便,先把文字放在一个span标签上,然后再放入容器(container)中,比如

text
,并且让容器的高度总是小于或等于文本内容的高度或者被文本内容拉伸,然后我们就可以设置容器的字体大小了。文字溢出判断文字是否超过N行设置固定字号或自动调整字号,需要准确判断文字是否超出。判断溢出的方法有很多种。这里我们获取当前文本的高度和N行文本允许的最大高度进行比较。通过container.scrollHeight可以得到当前文本的高度,通过文本的行高lineHeight*N可以得到N行文本允许的最大高度(单行文本的高度由文本的行高),最后比较两个大小得到出现文本溢出情况。/***@param{Number}lineNum最大允许文本行数*@param{htmlelement}containerEle文本容器*@returnsBoolean返回文本是否溢出最大允许文本函数*/functionisTextOverflow(lineNum,containerEle){让isOverflow=false;constcomputedStyle=getComputedStyle(containerEle);if(!/^\d+/.test(computedStyle.lineHeight)){thrownewError('container\'slineHeightmustbeanexactvalue!');}constlineHeight=Number(computedStyle.lineHeight.slice(0,-2));如果(containerEle.scrollHeight>Math.ceil(lineHeight*lineNum)){isOverflow=true;}returnisOverflow;}首先获取scrollHeight和lineHeight,注意使用该方法需要容器的lineHeight为明确的值。如果省略容器的lineHeight或者设置为关键字,则无法获取到具体值,最后返回两者的比较结果。N行文本缩小字体大小以适合容器。如果问题变成单行文本缩小字体大小以适应容器,它就会变得简单得多。我们只需要调整字体大小,让文本内容的宽度刚好等于容器的宽度,也就是我们需要得到当前文本的fontSize,当前文本不换行的宽度textWidth,当前容器宽度containerWidth,目标字体大小=fontSize*containerWidth/textWidth。获取文本未换行时的宽度最简单最简单的方法是先将容器的whiteSpace设置为nowrap,等待浏览器重新排列重绘得到未换行的宽度,然后重新设置whiteSpace,或者额外创建一个whiteSpace,将复制的nowrap元素插入到dom中,等待浏览器重排重绘即可获取。这样,我们就可以很容易地写出一个自动缩小单行文本字体大小以适应容器的方法。/***@param{*}containerEletextcontainer*@param{*}minFontSize限制可以缩小的最小字体大小*@returns*/functionadjustFontSizeSingle(containerEle,minFontSize=8){returnnewPromise(async(resolve)=>{if(!isTextOverflow(1,containerEle)){resolve();return;}constcomputedStyle=getComputedStyle(containerEle);constneedResetWhiteSpace=computedStyle.whiteSpace;constneedResetOverflow=computedStyle.overflow;.slice(0,-2));//设置文本不换行计算文本总长度containerEle.style.whiteSpace='nowrap';containerEle.style.overflow='隐藏';等待nextTick();consttextBody=containerEle.childNodes[0];if(containerEle.offsetWidth=measuredWidth){返回;}让firstTransFontSize=fontSize;让firstTransLineHeight=lineHeight;firstTransFontSize=Math.max(minFontSize,fontSize*(containerEle.offsetWidth/measuredWidth));firstTransLineHeight=firstTransFontSize/fontSize*lineHeight;fontSize=firstTransFontSize;containerEle.style.fontSize=`${fontSize}px`;如果(adjustLineHeight){lineHeight=firstTransLineHeight;containerEle.style.lineHeight=`${lineHeight}px`;}console.log('Overflowadjustmentcompleted');}Demonstration》当问题上升到多行文本时,也许我们可以通过计算(容器宽度*行数)与(不换行的文本总宽度)来得到需要缩小的文本的比例,但是并没有那么简单,因为文本的换行不是简单的把字符串分成等份打开每一行,而是将遵循排版换行的规则。具体规则参考UnicodeLineBreakingAlgorithm(UAX#14)UnicodeLineBreakingAlgorithm描述了一种算法,给定一个输入文本,将生成一组称为breakopportunities的位置,在文本渲染期间允许换行,但实际换行位置需要上位应用软件结合显示窗口宽度和字体大小单独确定也就是说,文本不会在每个字符处都换行,它会在通过算法得到的最接近换行机会的字符处换行。当然,浏览器也遵守这个规则。此外,还可以通过css3自定义一些换行规则-break:用于处理中文、日文、韩文(CJK)带标点符号的文本如何换行word-break:指定如何在单词连字符内换行:告诉浏览器如何在换行时用连字符连接单词overflow-wrap:用于表示浏览器是否允许这样的单词换行,以防止当无法分隔的字符串太长无法填充其换行时溢出盒子。幸运的是,我们不需要准确计算刚好填满容器的字体大小。我们可以先计算(容器宽度*行数)与(不换行的文本总宽度)的比值,初步得到文本需要缩小到的字体大小。受益于换行规则,此时文本可能仍然会溢出,但已经非常接近需要缩小以刚好填满容器的字号了。通过有限的循环次数缩小字号,在一定的误差范围内得到我们想要的。所需的字体大小。异步函数adjustFontSizeLoop(lineNum,containerEle,step=1,minFontSize=8,adjustLineHeight=false){letisOverflow=false;constcomputedStyle=getComputedStyle(containerEle);if(!/^\d+/.test(computedStyle.lineHeight)){thrownewError('container\'slineHeightmustbeaexactvalue!');}让lineHeight=Number(computedStyle.lineHeight.slice(0,-2));让fontSize=Number(computedStyle.fontSize.slice(0,-2));如果(containerEle.scrollHeight<=Math.ceil(lineHeight*lineNum)){返回;}consttextBody=containerEle.childNodes[0];如果(!offCtx){offCtx=document.createElement('canvas').getContext('2d');}const{fontFamily}=computedStyle;offCtx.font=`${fontSize}px${fontFamily}`;const{width:measuredWidth}=offCtx.measureText(textBody.innerText);如果(containerEle.offsetWidth*lineNum>=measuredWidth){返回;}让firstTransFontSize=fontSize;让firstTransLineHeight=lineHeigHT;firstTransFontSize=Math.max(minFontSize,fontSize*(containerEle.offsetWidth*lineNum/measuredWidth));firstTransLineHeight=firstTransFontSize/fontSize*lineHeight;fontSize=firstTransFontSize;containerEle.style.fontSize=`${fontSize}px`;如果(adjustLineHeight){lineHeight=firstTransLineHeight;containerEle.style.lineHeight=`${lineHeight}px`;}if(lineNum===1){返回;}让运行时间=0;做{等待nextTick();如果(containerEle.scrollHeight>Math.ceil(lineHeight*lineNum)){isOverflow=true;}else{isOverflow=false;}如果(!isOverflow){中断;}运行时间+=1;consttransFontSize=Math.max(fontSize-step,minFontSize);如果(adjustLineHeight){lineHeight=this.toFixed(transFontSize/fontSize*lineHeight,4);containerEle.style.lineHeight=`${lineHeight}px`;}fontSize=transFontSize;containerEle.style.fontSize=`${字体大小}px`;}while(isOverflow&&fontSize>minFontSize);console.log('溢出调整完成,循环设置字体大小:',runTime,'times');}Demonstration""调整前,调整限制为2行显示下一步?现在这个问题解决得不是那么完美(不确定循环次数的代码总是不太让人放心),看来效率和效果还过得去。是否可以一次计算出最终需要缩小的字体大小?我们可以首先尝试获取有关一段文本中断位置的信息。当然,我们没有那么多精力去手动实现换行规则,但是我们可以站在巨人的肩膀上,使用别人好的开源库:niklasvh/css-line-break。获取换行信息后如何计算?让我想一想。