хорошо, позвольте мне попытаться решить его;) на самом деле, думая о решении, я заметил, что недостаточно знаю о ваших требованиях, поэтому решил разработать простой код 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"
, возможно, что visible будет только первым ("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 Parser .Существует также один shag :
Хотя эта библиотека не охватывает весь спектр возможных странностей, которые предоставляет 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>