咳咳,这里是猫奴猴霸天,近期我遇到了一个需求让我研究很久,这个研究的过程很有意义,觉得有必要做下归纳整理,特此写下了这篇文章
需求前景
我有一个需求是开发一个文件选择器,这里的文件名称用户常常会起个周报或者日报的名称后边跟些特定的后缀做区分,但是当名字过长的时候,前端会文本溢出...显示,就如下图1,看起来那是一个一模一样无法区分,想要发出的时候根本不知道自己选对了没有,所以想要将...的溢出移到中间来,就如图2的效果来做对应的区分。
图1
图2
思考与实现
思考1我用slice直接截取前几个字符和后几个字符做拼接
这里我选择了直接截取,但是这里有三个痛点存在,一个是这个当时并没有出设计稿,并不知道这个名称的宽是非固定的,有三种不同的宽存在,定量截取并不能达到撑开整个标题位置的能力,第二就是这个标题是用户自己设置的,不一定是中文英文其他文或者特殊符号,所占位置不同,实现起来不太现实,最后里边含有搜索高亮的情况,会有标签格式的文本存在,这样截取是更更更不现实了,然后就直接pass了
思考2 弹性布局实现
其实最好的方式是slice截取但是考验的渲染压力有点高,算法又比较复杂平白加好多的代码,心里有点排斥这种写法所以想通过左右布局的形式达成
- 首先,判别是否超出一行需要进行特殊布局
const box = 要渲染的元素;//前提设置不换行
if(box.clientWidth<box.scrollWidth){
return 弹性布局的元素
}
return box;
- 其次对特殊布局的元素做特殊处理
//这里要渲染的元素依旧是我们的box,以react的形式进行书写,样式按内联形式写注意区分
<style>
.outBox{
//这里flex布局给定了高度宽度,高度是统一的高度
width: 100%;
display: flex;
justify-content: left;
height: 100%;
flex: 1;
}
.outBox .showLeft {
// 弹性布局占一半距离,但是这里的内容是需要换行的
flex: 1;
height: 100%;
overflow: hidden;
// 为了防止将英文或连贯的字符换行导致空出大片区域而设,使其可以在装不下时设置断行规则为在任意字符处断行
word-break: break-all;
//这里需要对第一行字的对齐方式设置向右
&::first-line{
text-align:right
}
}
.outBox .showRight {
//弹性布局占一半后,不可换行,文本水平溢出方向设置为右,溢出...
height: 100%;
flex: 1;
white-space: nowrap;
overflow: hidden;
direction: rtl;
text-overflow: ellipsis;
}
</style>
return <div className="outBox">
<div className="showLeft"> {box} </div>
<div className="showRight">{box} </div>
</div>
这样书写完成之后,发现我现在可以自适应布局了,对比方案一排除了设计稿的标题宽度不同的情况,但是又遇到新的一种情况,就是字节,我们给出的标题英文占一字节中文占俩字节点点点占俩字节但其实只有1.5字节,然后左右两侧并不一定宽刚好是字节的整数,导致渲染的时候会出现如下的情况,有一片小空白,有点不好看,emmmm,不过我个人觉得这个方法再研究研究应该可以解决掉,但是我的时间不够了,所以只能先pass然后结合算法实现
思考三通过算法实现
最后还是用了这种方式来实现了,好用是好用就是考虑因素有些多复用起来要求有点高,首先还是要判别是否超出一行达到我们要截取他的标准(见思考二);然后创建一个新的元素,通过不断的从中间截取字符然后渲染这个元素判别是否达到仅渲染一行的标准(目的不让用户感知页面飘忽)然后再将这个被处理的标题拿给元素渲染;最后要考虑高亮标签,这里用em标签代表高亮,从中间删除的时候要忽略em标签
//取得em标签的位置
const emShow = new Set();
let emLeftInd = 0;
let emRIghtInd = 0;
while (emLeftInd > -1 && emRightInd > -1) {
const leftInd = highlightTitle.indexOf('<em>', emLeftInd);
const rightInd = highlightTitle.indexOf('</em>', emRightInd);
if (leftInd > -1 && rightInd > -1) {
emShow.add(leftInd);
emShow.add(leftInd + 1);
emShow.add(leftInd + 2);
emShow.add(leftInd + 3);
emShow.add(rightInd);
emShow.add(rightInd + 1);
emShow.add(rightInd + 2);
emShow.add(rightInd + 3);
emShow.add(rightInd + 4);
emLeftInd = leftInd + 4;
emRightInd = rightInd + 5;
} else {
emLeftInd = -1;
emRightInd = -1;
}
}
//取中间位,这里取值中间位判别不是em标签往后移动
let titleCenter = parseInt(String((title || '')?.length / 2), 10);
let content = 0;
let clipInd = 0;
highlightTitle.split('').forEach((ite, ind) => {
if (content === titleCenter) {
return;
}
clipInd = ind;
if (!emShow.has(ind)) {
content += 1;
}
});
//创建新的元素不显示与界面防止显示抖动来判别最终是否位置足够
const newDom = document.createElement('div');
const showTitleArr = highlightTitle.split('');
newDom.style.fontSize = '14px';
newDom.style.visibility = 'hidden';
newDom.style.position = 'absolute';
newDom.style.zIndex = '0';
document.body.appendChild(newDom);
// 进行递归截取字符串,第一次时替换为。。。之后截取
const callback = (leftInd: number, rightInd: number, chang?: boolean) => {
newDom.innerText = showTitleArr.join('').replace(/<em>|</em>/g, '');
const clientWidth = newDom.clientWidth;
if (clientWidth > boxWidth) {
let showLeftInd = leftInd;
let showRightInd = rightInd;
while (emShow.has(showLeftInd)) {
showLeftInd -= 1;
}
while (emShow.has(showRightInd)) {
showRightInd += 1;
}
if (chang) {
showTitleArr[showLeftInd] = '...';
} else {
showTitleArr[showLeftInd] = '';
showTitleArr[showRightInd] = '';
}
if (showLeftInd > 0 && showRightInd < highlightTitle.length) {
callback(showLeftInd - 1, showRightInd + 1);
}
} else {
return true;
}
};
//进行第一次截取
callback(clipInd, clipInd, true);
//最终showTitleArr.join("")就是我们的结果
虽然最终通过这种形式完成了,展示效果也达到了需求,但是对于最后的渲染形式以及渲染过程的压力还是有些不太满意,每次删两个字就得重新渲染一次然后重新计算,虽然它用定位实现为一个BFC对其他元素的影响并不大,但是还是有一定的回流重绘,而且对复用的要求有点高,如果可以以后会再研究研究将思考二更完美的实现出来
总结
综上考虑多元化需求的时候暂时还是使用算法的形式进行实现,但是操作数据并不复杂的时候可以使用思考一二来实现。