2024-03-16 小汇总
1. 文本自适应超出省略
单行文本超出省略时,通常需要指定一个固定的宽度,或者使用百分比宽度。比如下面的 div 元素,虽然没有指定宽度,但是他会继承父元素的宽度,超出出现省略符。
<div>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Et, ducimus eaque laborum nostrum sed cumque optio quisquam nihil soluta aliquid! Iste officiis delectus distinctio veniam? Recusandae consectetur sit pariatur saepe!
</div>
<style>
div {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>
但有时候我们可能会遇到下面这种布局,父容器的宽度不是固定的,中间的文本内容会自动超出省略,而它两侧的内容可以正常展现。
这时候就可以使用 flex 布局实现:
<div class="wrapper">
<span class="icon">+</span>
<span class="content">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nam, ab molestiae deleniti beatae neque commodi reprehenderit illum dolore doloremque inventore quas saepe nobis.</span>
<span class="tag">重点</span>
</div>
<style>
html {
font-size: 14px;
}
* {
box-sizing: border-box;
}
.wrapper {
width: 20%;
min-width: 15rem;
/** 设置为 flex 布局 */
display: flex;
gap: 1em;
/** 垂直对齐 */
align-items: center;
padding: 0.5em;
border-radius: 0.5em;
border: 1px solid rgba(0, 0, 0, 0.15);
}
.icon {
display: block;
/** 该子元素将不会收缩 */
flex-shrink: 0;
flex-basis: 1.2rem;
border-radius: 50%;
height: 1.2rem;
line-height: 1.2rem;
text-align: center;
border: 1px solid #ddd;
}
.content {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.tag {
display: inline-block;
padding: 0.25em 0.75em;
border-radius: 0.25em;
background-color: rgba(75, 0, 130, 0.05);
color: indigo;
/** 该子元素将不会收缩,防止换行 */
flex-shrink: 0;
}
</style>
上面代码中核心的代码是:
- 给
wrapper元素设置 flex 布局; - 给
icon、tag元素设置flex-shrink: 0;这样就不会因为content元素宽度过长而导致这两个元素缩放; - 给
content元素设置超出省略;
除了给两侧元素设置 flex-shrink: 0; 之外,还可以只针对 content 设置这下面这两个属性,这样也能实现我们想要的效果。
flex-grow: 1;
flex-basis: 0;
flex-grow 的默认值是 0,当只给 content 元素设置成 1 时,他就会把剩余空间全部占满,但因为 flex-shrink 的默认值是 1,tag 和 icon 元素的宽度就会被收缩。如果再给 content 的初始宽度设置为 0(flex-basis: 0;),则会把剩余的最小宽度应用到 content 元素上。
2. RTL 超出省略
RTL(从右到左,Right To Left)是区域设置 (en-US)的属性之一,用于指示语言从右到左的书写方向。比如阿拉伯语、希伯来语等。
只要再超出省略的基础上加上一个 direction: rtl; 属性就可以了。
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure voluptatum id officia distinctio, nihil hic quo sed, perferendis magni minus laboriosam nulla veniam in reiciendis. Hic reprehenderit asperiores obcaecati sint.
</div>
<style>
.content {
direction: rtl;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
但有些符号可能并不遵循这个规则,比如 * 这个符号。比如下面的文案,在末尾处或者开头的时候有 * 号,显示的位置就出了问题。
*Lorem ipsum *dolor sit amet.****
对于*这个符号,它通常被视为与文字的方向无关的内容,因此在RTL语言中并不会按照正常的文字方向排列。
但想象一个这样的场景:我们要设计一个类似于计算器的按键程序,按下虚拟键盘里的字符时上方的显示器就会显示出输入的内容。

当输入内容超出时就会把左侧旧的输入省略(...)掉。
解决办法是可以在输入字符时,利用 js 在这个字符之间插入 ‎ 转义字符,告诉浏览器当前的字符用从左往右的排版,但在 css 中我们使用了 direction: rtl; 属性,这时候特殊字符就不会跑到别的位置去了。
下面代码展示了虚拟按键显示屏的核心代码(TSX):
<span style={{direction: 'rtl'}}>
‎{
charList.map(char => {
return <span>{char}‎</span>
})
}
</span>
3. 在 Popover 组件中输入中文
在 Antd 中的 popover 中使用 Input 组件时,如果输入中文会遇到下面这种情况,鼠标移动到输入法上面时,气泡窗口自动消失了。

一个解决办法是开始新的输入合成时(触发 compositionstart 事件)将 Popover trigger 属性里的 hover 交互形式去掉,当输入完成后(触发 compositionend 事件)再把 hover 交互添加进来。
const App = () => {
const [trigger, setTrigger] = useState(["hover", "click"]);
return (
<Popover
trigger={trigger}
title="Popover"
content={
<Input.TextArea
onCompositionStart={() => {
setTrigger(["click"]);
}}
onCompositionEnd={() => {
setTrigger(["hover", "click"]);
}}
/>
}
>
<Button>Hover</Button>
</Popover>
);
};
4. 双击编辑文本内容
比如下面的效果,有一个容器,当文本超出时会自动省略,当双击时可以全选和编辑里面的内容,当失去焦点或者按下 Enter 键后编辑的内容就会应用上去。

思路:
- 文本内容可以用 div 包裹;
- 监听 div
DoubleClick事件; - 触发双击事件时,动态创建 input 元素;
- 把 div 中的内容塞到 input 框里,并把 div 中的内容清空;
- 把 input 元素添加到 div 中;
- 监听 input 框的 keydown 事件(是否按下了
Enter键)和 blur 事件; - 当按下
Enter键或者失去焦点时,取出 input 中的内容,移除 input 元素,并把内容重新添加到 div 里;
代码如下:
/**
* 文本容器
* @type {HTMLDivElement}
*/
const div = document.querySelector('.content');
/** 是否已经生成了 input 元素 */
const HAS_INPUT_ATTR = 'data-has-input';
div.ondblclick = () => {
const hasInput = div.getAttribute(HAS_INPUT_ATTR);
if (hasInput) return;
div.setAttribute(HAS_INPUT_ATTR, "true");
// 创建 input 元素
const input = document.createElement('input');
input.setAttribute('type', 'text');
// 把 div 中的内容赋给 input
input.value = div.textContent;
// 清楚 div 里的内容
div.textContent = '';
// 更改输入框样式
input.classList.add('contentInput');
// 输入完成后,删除 input,更新 div 的内容
const inputEnd = () => {
const text = input.value.trim();
input.remove();
div.textContent = text;
div.removeAttribute(HAS_INPUT_ATTR);
}
// 监听 input 变化,如果 blur,则删除 input,并更新 div 的内容
input.onblur = inputEnd;
// 监听键盘输入
input.onkeydown = (e) => {
if (e.key === 'Enter') {
inputEnd();
}
}
div.appendChild(input);
// 自动聚焦,并自动全选内容
input.focus();
input.select();
}
样式:
html {
font-size: 14px;
}
* {
box-sizing: border-box;
}
.content {
width: 10rem;
overflow: auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0.5em 1em;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5em;
}
/* 颜色和字体大小继承父元素 */
.contentInput {
outline: none;
border: none;
height: 100%;
width: 100%;
color: inherit;
font-size: inherit;
}
除了上面的思路外,也可以利用 contenteditable 属性实现,这不需要单独创建 input 元素,缺点可能就是有一个滚动条,影响外观,而且修改滚动条样式需要考虑浏览器兼容问题。
思路主要是:
- 双击时,给 div 元素添加
contenteditable属性,并且把overflow属性改成auto; - 自动聚焦和自动选中;
- 失去焦点或者按下
Enter键后,把contenteditable属性去掉,并更改overflow属性为hidden;
代码如下:
div.ondblclick = () => {
div.setAttribute('contenteditable', 'true');
div.classList.add('edit');
const editEnd = () => {
div.classList.remove('edit');
// 滚动到行首
div.scrollTo(0, 0);
div.removeAttribute('contenteditable', 'true');
}
div.onkeydown = (e) => {
if (e.key === 'Enter') {
editEnd();
}
}
div.onblur = editEnd;
div.focus();
selectText(div);
}
/**
* 选中文本内容
* @param {Node} node
*/
const selectText = (node) => {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
};
需要注意的是:
- 当不再编辑时记得把滚动条滚动会行首(scrollTo);
- div 元素上没有
select()方法,我们需要借助Selection和Range这两个 API 选中文本。- createRange() 会创建一个
Range; - selectNodeContents() API 用于选取内容;
- removeAllRanges() API 会从当前 selection 对象中移除所有的 range 对象,取消所有的选择;
- addRange() API 用于选中指定范围地文本;
- createRange() 会创建一个
CSS:
.content {
width: 10rem;
overflow: auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0.5em 1em;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5em;
/* 隐藏滚动条 */
scrollbar-width: none;
/* 编辑聚焦时去除轮廓 */
outline: none;
}
/* 不显示滚动条 */
.content::-webkit-scrollbar {
display: none;
}
.content.edit {
overflow: auto;
text-overflow: initial;
}