乱序的DOM,正序的文字
故事是这样开始的,今天一早,我正在沸点闲逛,忽见一位同学提了个问题,类似下图

咦,怎么 DOM 里面的文字完全是乱序的,可是页面上的文字却能正常显示呢?
嘻嘻,这个“反复制”操作好像有点意思,出于害死了猫的那个理由,我决定自己尝试实现下。
简单版
俗话说,先生存,再生活。
所以,我首先意思意思搞了个简单的版本,文字只有一行,先试试水吧。
先来定义个容器 container:
<head>
<style>
#container {
position: relative;
height: 20px;
border: 1px solid #bdbdbd;
border-radius: 3px;
background: ivory;
color: #708090;
font-family: 'microsoft yahei';
user-select: none;
}
#container > span {
position: absolute;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
我的实现思路是这样的:
- 把文字拆成单个,给每个文字套上
<span>标签 - 根据文字的位置给
span标签加上偏移量 - 设置文字都相对于其父元素绝对定位
var container = document.querySelector('#container')
var fontSize = 16
var text = '反者道之动'
var textArr = text.split('')
var html = textArr
.map((s, i) => {
// 根据每个文字的位置下标,设置该文字相对于父元素该向左移多少像素
var left = fontSize * i
return `<span style="left: ${ left }px;">${s}</span>`
})
// 打乱文字的顺序
.sort(() => Math.random() - 0.5)
.join('')
// 插入文档中
container.innerHTML = html
哒哒!简单版到此就完成啦。是不是超简单呢?
但仔细look一look,这个 demo 好像太简陋了点,只能处理单行文字,这哪行啊!
所以我决定升级一下它,让它也能支持多行文字。毕竟,解决了温饱问题,就会开始觊觎滋润的小日子了嘛。
多行文字版
其实,要支持多行文字一点也不复杂。
首先,我们需要先计算出 container 一行能放下多少个文字。
这还不简单嘛,只要我们知道 container 的宽度和每个文字所占的宽度,剩下的不就是小学鸡数学吗,难不倒我的。
// 获取 container 的宽度
var width = container.getBoundingClientRect().width
// 设置文字大小
var fontSize = 16
// 算术题:一根香蕉长x,一颗李子直径为y,请问一根香蕉里面能塞下多少个李子呢
var charPerLine = Math.floor(width / fontSize)
解出了第一道小学鸡算术题,下一步,我们来算一算一段文字可以分多少行。
var textTotal = text.length
var lines = Math.ceil(textTotal / charPerLine)
接下来可是重点,我们要算出每个文字相对父元素的位置。
在简单版里,我们只有一行文字,所以只需要文字的 X 轴偏移量。但在多行文字版中,我们需要考虑二维坐标啦。其实,再多看一眼,也还是小学鸡数学嘛。
textArr.map((s, i) => {
// 先计算当前文字在第几行
var line = Math.ceil((i + 1) / charPerLine)
// 再计算文字位于当前行的第几个
var indent = (i + 1) % charPerLine || charPerLine
// Y 轴偏移量
var top = lineHeight * (line - 1)
// X 轴偏移量
var left = fontSize * (indent - 1)
})
咦?是算出来了吗?好像是真的呢,Yeah。好啦,接下来的步骤就和简单版的一样啦。
问题1:窗口 resize 的时候,文字不会跟着流动?
这个问题很好解决的啦,只要监听 resize 事件,动态获取 container 的宽度做计算就好啦。
问题2:子元素都绝对定位了,父元素高度不就坍塌了嘛?
那我们给父元素动态设置个高度吧
container.style.height = Math.ceil(text.length / charPerLine) * lineHeight + 'px'
逆向
最后,顺便来玩一下把乱序的 HTML 转换成正序的文字吧。
var htmlText = container.innerHTML.replace(/\s+/g, ' ')
// 获取每个 span 的 top, left 偏移和内容,其实...写到这里...我突然想起了更简单的方法-_-
// 直接操作 DOM 不比操作字符串简单直接得多吗,我和字符串较什么劲呀
var charArr = htmlText.match(/<span.*?<\/span>/gi).map(el => {
return {
top: +el.match(/top:\s*(\d+)/i)[1],
left: +el.match(/left:\s*(\d+)/i)[1],
char: el.match(/>\s*(.*)\s*<\/span>/)[1],
}
})
// 根据 top 和 left 偏移量给文字排序
charArr.sort((a, b) => {
return a.top - b.top || a.left - b.left
})
var originText = charArr.map(el => el.char).join('')
console.log(originText)
操作 DOM 的写法:
var charArr = []
container.querySelectorAll('span').forEach(el => {
charArr.push({
top: el.style.top.match(/\d+/)[0],
left: el.style.left.match(/\d+/)[0],
char: el.innerText
})
})
完整代码
最后给个干巴巴的JSBin链接。
以上です。