我有一个包含大量文本的字符串, 文本,在我的JavaScript文件中。我也有一个元素, DIV#集装箱 使用可能非标准的样式(使用单独的CSS) line-height
, font-size
, font-face
,也许还有其他人。它有一个固定的高度和宽度。
我想获得适合的最大文本量 DIV#集装箱 没有任何字符串溢出。这样做的最佳方法是什么?
这需要能够处理使用标签格式化的文本,例如:
<strong>Hello person that is this is long and may take more than a</strong>
line and so on.
目前,我有一个适用于纯文本的JQuery插件,代码如下:
// returns the part of the string that cannot fit into the object
$.fn.func = function(str) {
var height = this.height();
this.height("auto");
while(true) {
if(str == "") {
this.height(height);
return str; // the string is empty, we're done
}
var r = sfw(str); // r = [word, rest of String] (sfw is a split first word function defined elsewhere
var w = r[0], s = r[1];
var old_html = this.html();
this.html(old_html + " " + w);
if(this.height() > height)
{
this.html(old_html);
this.height(height);
return str; // overflow, return to last working version
}
str = s;
}
}
更新:
数据如下所示:
<ol>
<li>
<h2>Title</h2>
<ol>
<li>Character</li>
<ol>
<li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
<li>Line two can be separated from line one, but not from itself</li>
</ol>
</ol>
<ol>
<li>This can be split from other</li>
<ol>
<li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
<li>Line two can be separated from line one, but not from itself</li>
</ol>
</ol>
</li> <li>
<h2>Title</h2>
<ol>
<li>Character</li>
<ol>
<li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
<li>Line two can be separated from line one, but not from itself</li>
</ol>
</ol>
<ol>
<li>This can be split from other</li>
<ol>
<li>Line one that might go on a long time, SHOULD NOT BE BROKEN</li>
<li>Line two can be separated from line one, but not from itself</li>
</ol>
</ol>
</li>
</ol>
好吧,让我试着解决它;)实际上考虑解决方案我注意到我对你的要求知之甚少,所以我决定开发简单的JavaScript代码并向你展示结果;尝试之后你可以告诉我什么是错的,所以我可以修理/改变它,交易?
我使用纯JavaScript,没有jQuery(如果需要可以重写)。原理类似于你的jQuery插件:
- 我们逐个取字符(而不是单词)
sfw
功能呢;它可以改变)
- 如果它是开始标记的一部分,浏览器不会显示它,所以我没有特别处理它,只是从标记名称和容器的检查高度逐个添加...不知道是不是那么糟糕。我的意思是当我写作
container.innerHTML = "My String has a link <a href='#'";
在浏览器中,我看到“My String has a link
“,所以”未完成“标签不会影响容器的大小(至少在我测试过的所有浏览器中)
- 检查容器的大小,如果它大于我们预期的大小,那么之前的字符串(实际上当前字符串没有最后一个字符)就是我们要找的东西
- 现在我们必须关闭所有因切割而未关闭的开口标签
用于测试它的HTML页面:
<html>
<head>
<style>
div {
font-family: Arial;
font-size: 20px;
width: 200px;
height: 25px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"> <strong><i>Strong text with <a href="#">link</a> </i> and </strong> simple text </div>
<script>
/**
* this function crops text inside div element, leaving DOMstructure valid (as much as possible ;).
* also it makes visible part as "big" as possible, meaning that last visible word will be split
* to show its first letters if possible
*
* @param container {HTMLDivElement} - container which can also have html elements inside
* @return {String} - visible part of html inside div element given
*/
function cropInnerText( container ) {
var fullText = container.innerHTML; // initial html text inside container
var realHeight = container.clientHeight; // remember initial height of the container
container.style.height = "auto"; // change height to "auto", now div "fits" its content
var i = 0;
var croppedText = "";
while(true) {
// if initial container content is the same that cropped one then there is nothing left to do
if(croppedText == fullText) {
container.style.height = realHeight + "px";
return croppedText;
}
// actually append fullText characters one by one...
var nextChar = fullText.charAt( i );
container.innerHTML = croppedText + nextChar;
// ... and check current height, if we still fit size needed
// if we don't, then we found that visible part of string
if ( container.clientHeight > realHeight ) {
// take all opening tags in cropped text
var openingTags = croppedText.match( /<[^<>\/]+>/g );
if ( openingTags != null ) {
// take all closing tags in cropped text
var closingTags = croppedText.match( /<\/[^<>]+>/g ) || [];
// for each opening tags, which are not closed, in right order...
for ( var j = openingTags.length - closingTags.length - 1; j > -1; j-- ) {
var openingTag;
if ( openingTags[j].indexOf(' ') > -1 ) {
// if there are attributes, then we take only tag name
openingTag = openingTags[j].substr(1, openingTags[j].indexOf(' ')-1 ) + '>';
}
else {
openingTag = openingTags[j].substr(1);
}
// ... close opening tag to have valid html
croppedText += '</' + openingTag;
}
}
// return height of container back ...
container.style.height = realHeight + "px";
// ... as well as its visible content
container.innerHTML = croppedText;
return croppedText;
}
i++;
croppedText += nextChar;
}
}
var container = document.getElementById("container");
var str = cropInnerText( container );
console.info( str ); // in this case it prints '<strong><i>Strong text with <a href="#">link</a></i></strong>'
</script>
</body>
可能的改进/变化:
- 我不创建任何新的DOM元素,所以我只是重用当前的容器(以确保我考虑到所有的CSS样式);这样我就会一直改变它的内容,但是在获取可见文本后你就可以写了
fullText
如果需要可以回到容器中(我也不会改变)
- 逐字处理原始文本将让我们对DOM进行较少的更改(我们将逐字逐句地逐字逐句地写),因此这种方式应该更快。你已经拥有了
sfw
功能,所以你可以轻松改变它。
- 如果我们有两个字
"our sentence"
,有可能只有第一个可见("our"
),并且应该削减“句子”(overflow:hidden
会这样工作)。在我的情况下,我将逐个字符追加,所以我的结果可以 "our sent"
。同样,这不是算法的复杂部分,因此基于您的jQuery插件代码,您可以更改我的单词。
问题,评论,发现的错误是受欢迎的;)我在IE9,FF3.6,Chrome 9中进行了测试
更新: 遇到问题 <li>, <h1>
......例如我有容器内容:
<div id="container"> <strong><i>Strong text with <ul><li>link</li></ul> </i> and </strong> simple text </div>
在这种情况下,浏览器的行为方式(逐个字符串,容器中的内容以及根据算法显示的内容):
...
"<strong><i>Strong text with <" -> "<strong><i>Strong text with <"
"<strong><i>Strong text with <u" -> "<strong><i>Strong text with "
"<strong><i>Strong text with <ul" -> "<strong><i>Strong text with <ul></ul>" // well I mean it recognizes ul tag and changes size of container
算法的结果是字符串 "<strong><i>Strong text with <u</i></strong>"
- 用 "<u"
什么不好。在这种情况下我需要处理的是,如果我们找到了结果字符串("<strong><i>Strong text with <u"
根据算法),我们需要删除最后一个“未闭合”标签("<u"
在我们的例子中),所以在关闭标签以获得有效的html之前我添加了以下内容:
...
if ( container.clientHeight > realHeight ) {
/* start of changes */
var unclosedTags = croppedText.match(/<[\w]*/g);
var lastUnclosedTag = unclosedTags[ unclosedTags.length - 1 ];
if ( croppedText.lastIndexOf( lastUnclosedTag ) + lastUnclosedTag.length == croppedText.length ) {
croppedText = croppedText.substr(0, croppedText.length - lastUnclosedTag.length );
}
/* end of changes */
// take all opening tags in cropped text
...
可能有点懒惰的实现,但它可以调整,如果它减慢。这里做了什么
- 不带所有标签
>
(在我们的例子中,它返回 ["<strong", "<i", "<u"]
);
- 拿最后一个(
"<u"
)
- 如果它结束了
croppedText
字符串,然后我们删除它
完成后,结果字符串变为 "<strong><i>Strong text with </i></strong>"
UPDATE2 谢谢你的例子,所以我看到你没有嵌套标签,但它们也有“树”结构,实际上我没有考虑到它,但它仍然可以修复;)一开始我想要编写我的相应“解析器”,但是当我不工作的时候我总是得到一个例子,所以我认为最好找到已编写的解析器,并且有一个: 纯JavaScript HTML解析器。还有一个 粗毛 它:
虽然这个图书馆没有涵盖
完全可能的奇怪之处
HTML提供,它确实处理了很多
最明显的东西。
但是对于你的例子它是有效的;那个图书馆没有考虑开放标签的位置,但是
- 我们依靠原始的html结构很好(没有破坏);
- 我们在结果“字符串”的末尾关闭标签(所以这没关系)
我认为有了这个假设,这个库很好用。然后结果函数看起来像:
<script src="http://ejohn.org/files/htmlparser.js"></script>
<script>
function cropInnerText( container ) {
var fullText = container.innerHTML;
var realHeight = container.clientHeight;
container.style.height = "auto";
var i = 0;
var croppedText = "";
while(true) {
if(croppedText == fullText) {
container.style.height = realHeight + "px";
return croppedText;
}
var nextChar = fullText.charAt( i );
container.innerHTML = croppedText + nextChar;
if ( container.clientHeight > realHeight ) {
// we still have to remove unended tag (like "<u" - with no closed bracket)
var unclosedTags = croppedText.match(/<[\w]*/g);
var lastUnclosedTag = unclosedTags[ unclosedTags.length - 1 ];
if ( croppedText.lastIndexOf( lastUnclosedTag ) + lastUnclosedTag.length == croppedText.length ) {
croppedText = croppedText.substr(0, croppedText.length - lastUnclosedTag.length );
}
// this part is now quite simple ;)
croppedText = HTMLtoXML(croppedText);
container.style.height = realHeight + "px";
container.innerHTML = croppedText ;
return croppedText;
}
i++;
croppedText += nextChar;
}
}
</script>
要获得最长的第一行:
- 用它创建一个DIV
visibility:hidden;
(因此它将具有尺寸)但将其定位为 position:absolute;
所以它不会破坏你的页面流
- 将其类型样式设置为与结果相同的值
DIV
- 设置它的高度与结果相同
DIV
但保持 width:auto;
- 添加文字
- 保持切断文字直到宽度下降到最终结果
DIV
的宽度。
结果是您可以放入的文本。
如果需要查找数量,请调整算法 线 适合容器保存 height:auto;
并设置固定 width
。
自动调整textareas使用相同的技术,当用户键入文本时,textareas会自动增长。
要解决此问题,您将需要其他信息:
- 我应该在哪里“切”输入文字
- 切碎它,我该如何修复这两半,以便我可以把每一个都塞进DIV?
至于“在哪里砍”问题,你可能需要注入独特的 <a name="uniq"/>
在输入字符串中的关键点处锚定标记(比如......在输入中的每个开始标记之前?)。然后,您可以测试每个锚点的布局位置并找到中断输入的位置。
找到最合理的断点,你需要在前半部分的末尾添加标签以关闭它,并在下半部分的前面添加标签以打开它。因此,当您解析输入字符串以便先找到开始标记时,您在注入时会保留一个“标记堆栈”列表 <a/>
。查找与此paritcular相关的标记堆栈,然后根据需要添加标记。
我可以发现2个陷阱:
- 如果输入标签具有属性,则需要保留有关每个中断的更多信息
- 您可能需要将某些标签视为“牢不可破”并在之前进行中断
<a/>
代替
最终,在我看来,你正在等待HTML5的列构造。
你只是想格式化 第一行 在一个段落?请问CSS :第一行 伪选择器适用于您的应用程序?
你可以用 的getComputedStyle 找出内联元素的宽度(即它有 display: inline
属性):
window.getComputedStyle(element, null).getPropertyValue("width");
如果事实证明此元素的宽度大于其容器的宽度,则会逐步删除一些文本,直到它适合。