Ответ 1
ну, позвольте мне попытаться это решить;) на самом деле думая о решении, я заметил, что я недостаточно знаю о ваших требованиях, поэтому решил разработать простой код 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"
в нашем случае), поэтому перед закрытием тегов valid 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 Parser. Существует также один шаг:
Хотя эта библиотека не охватывает полная гамма возможных странностей, которые HTML обеспечивает, он обрабатывает много наиболее очевидный материал.
но для вашего примера это работает; эта библиотека не учитывала положение открывающего тега, но
- мы полагаем, что оригинальная структура html прекрасна (не нарушена);
- мы закрываем теги в конце результата "string" (так это нормально)
Я думаю, что с этими предположениями эту библиотеку приятно использовать. Тогда функция результата выглядит так:
<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>