下面是iMessage的照片,很抱歉,它太大了。在图像中,您将看到不同的多行消息具有不同的宽度。实际上,每个似乎都针对最小宽度进行了优化,而无需创建换行符。
以下是一些代码,它会非常缓慢地实现此效果。
// finds the minimum width an element can be without becoming taller
function minimizeWidth(domNode) {
if (domNode.offsetWidth < 160) { return; }
const squinchFurther = () => {
const startHeight = domNode.offsetHeight;
const startWidth = domNode.offsetWidth;
if (startWidth === 0) {
return;
}
domNode.style.width = (startWidth - 1) + "px";
// wait for reflow before checking new size
requestAnimationFrame(() => requestAnimationFrame(() => {
// if the height has been increased, go back
if (domNode.offsetHeight !== startHeight) {
domNode.style.width = startWidth + "px";
} else {
squinchFurther();
}
}));
}
requestAnimationFrame(() => requestAnimationFrame(squinchFurther));
}
const divs = document.querySelectorAll("div");
for (let i = 0; i < divs.length; i++) {
minimizeWidth(divs[i]);
}
div {
box-sizing: border-box;
display: inline-block;
max-width: 160px;
padding: 5px;
border-radius: 5px;
margin: 10px;
background: #08F;
color: white;
}
<div>Here's some multi line text</div>
<br>
<div>Word</div>
<br>
<div>Crux case a a a a a a a a</div>
是否有任何CSS可以自动执行此操作?如果没有,是否有一种方法可以在JS中计算而无需等待重排?
我记得曾经看到过一些可以用WASM编码的“回流工作器”的东西,但是我现在找不到任何东西。如果有人知道我在说什么,请分享链接。
答案 0 :(得分:1)
据我所知,仅凭CSS不可能做到这一点。下面的解决方案将每个文本块保持在简单的<div>
+ <span>
结构中,然后使用getBoundingClientRect()
测量<span>
的宽度并将其更新为{{1} },并具有正确的宽度。
看起来好像肯定有一个最大宽度,用于换行,也就是说,如果“ McCormick”或“ interesting”在前一行上,则宽度太长。我不相信我曾经见过超过屏幕宽度约75%的消息。我为此演示设置了最大宽度为160px。
请注意,有两个display:block
循环,因此可以缓存宽度,因此我们不会连续读取然后写入DOM(并导致多次重排)。
for
function updateWidths() {
const elems = document.querySelectorAll('.inner');
const len = elems.length;
const widths = [];
// Read from the DOM
for (let i = 0; i < len; i++) {
widths.push(elems[i].getBoundingClientRect().width);
}
// Write to the DOM
for (let i = 0; i < len; i++) {
elems[i].style.display = 'block';
elems[i].style.width = widths[i] + 'px';
}
}
updateWidths();
.outer {
margin-top: 10px;
max-width: 160px;
}
.inner {
background-color: blue;
border-radius: 5px;
color: white;
padding: 5px;
}
答案 1 :(得分:0)
我发现了一个技巧,只能用于一次回流,但这有点麻烦。它的基本要点是:
// helper fns from npm el-tool
const div = (children: HTMLElement[]) => {
const out = document.createElement("div");
children.forEach((child) => out.appendChild(child));
return out;
}
const span = (text: string) => {
const out = document.createElement("span");
out.innerText = text;
return out;
}
export default function minimalWidthDiv(innerText: string) {
// split string into words with following spaces included
const wordEls = innerText.trim().match(/\S+\s+/g).map(span);
const thinDiv = div(wordEls);
// set to hidden while computing width to avoid thrashy renders
thinDiv.style.visibility = "hidden";
// wait for first render
requestAnimationFrame(() => requestAnimationFrame(() =>
const numberOfLines = magicFnThatFindsNumberOfLines();
const currentWidth = thinDiv.offsetWidth;
const minimalWidth = currentWidth/numberOfLines;
let bestWidth = 0;
// figure out the best width based of the widths of the words
// if more than two lines, the loop below won't work is most cases
for (let i = 0; i < wordEls.length && bestWidth < minimalWidth; i++) {
bestWidth += wordEls[i].offsetWidth;
}
// update the width of the thinDiv and make it visible.
thinEl.style.width = bestWidth + "px";
thinEl.style.visibility = "";
));
return thinDiv;
}
这里的窍门是将所有单词放入单独的跨度中,以便可以计算其宽度。从那里,无需重新换行就可以算出最小宽度。