背景
有时我们需要实现这样一种布局,将一系列词组放到一个容器里进行展示,但是不要在词组内部产生截断,主要用作静态文案展示:
自然的单词分界可以提高阅读效率,使信息获取更为高效。
前置准备
假如我们有以下代码(Vue):
<template>
<div>
<h2>正常截断</h2>
<div class="text-container">{{ text }}</div>
<h2>CSS实现单词自然换行</h2>
<div class="text-container" v-html="cssFormattedText"></div>
<h2>JS实现单词自然换行</h2>
<div class="text-container">{{ jsFormattedText }}</div>
</div>
</template>
<script>
export default {
data() {
return {
rawText: "2024年12月31日 16:00-18:00,2025年01月01日 16:00-18:00、19:00-20:00,2025年01月02日 16:00-18:00、19:00-20:00、21:00-22:00"
};
},
computed: {
text() {
return this.rawText.split(/[,,]/).join('\n')
}
}
}
</script>
<style>
* {
margin: 0;
}
h2 {
margin-left: 20px;
}
.text-container {
margin: 20px;
width: 400px;
padding: 10px;
border: 1px solid #f0f0f0;
white-space: pre-wrap;
}
.text-line {
display: inline-flex;
flex-wrap: wrap;
}
#measure-box {
height: 0;
padding: 0;
border: none;
overflow: hidden;
visibility: hidden;
white-space: nowrap;
}
</style>
正常截断的文案长这样:
CSS实现
为了通过CSS实现单词的自然换行,我们可以文本进行如下操作:
- 将文本拆分成行
- 对行内文案进行切词
- 将切词后的单词用标签包裹
- 利用flex布局进行渲染
export default {
data() {
return {
rawText: "2024年12月31日 16:00-18:00,2025年01月01日 16:00-18:00、19:00-20:00,2025年01月02日 16:00-18:00、19:00-20:00、21:00-22:00"
};
},
computed: {
text() {
return this.rawText.split(/[,,]/).join('\n')
},
cssFormattedText() {
// 文案分行
return this.rawText.split(/[,,]/).map(line => {
// 切词
const text = line.match(/[\s、]|[^\s、]+/g)
?.map(item => `<span class="word">${item}</span>`)
.join('')
return `<p class="text-line">${text}</p>`
}).join('')
}
}
}
渲染后的HTML结构:
实现效果:
JS实现
CSS实现的自然换行不太好控制每一行中最后一个分词符的位置,比如上面的 "、",他可能会跑到下一行,也可能在本行展示:
通过JS实现则不会有上述"问题",同样的,JS实现单词自动换行的效果主要有以下步骤:
- 创建一个和容器宽度一致且不可换行、不可见的测量容器
- 对文案进行拆行
- 将每一行的文案放进测量容器里进行测量
- 如果当前行无法完整放置所有单词,则把最后一个单词放到下一行(新起一行)
- 重复第3步
computed: {
jsFormattedText() {
const el = document.getElementById('measure-box')
if (!el) {
return ''
}
const lines = this.rawText.split(/[,,]/)
const cache = {}
let i = 0
const split = (id = '、') => {
const subs = lines[i].split(id)
if (subs.length > 1 && el.scrollWidth > el.clientWidth) {
if (!cache[i]) {
lines.splice(i, 1, subs.slice(0, -1).join(id), subs.at(-1))
} else {
lines.splice(i, 1, subs.slice(0, -1).join(id))
lines[i + 1] = subs.at(-1) + id + lines[i + 1]
}
cache[i] = 1
return true
}
return false
}
while (i < lines.length) {
el.innerText = lines[i]
if (split('、') || split(' ')) {
continue
}
i += 1
}
return lines.join('\n')
}
}
渲染效果: