Css троеточие в конце. Обрезка одно-, или многострочного текста по высоте с добавлением многоточия. Добавляем градиент к тексту

Как правило, когда нужно выполнить какие-либо действия с DOM, разработчики используют jQuery. Однако практически любую манипуляцию с DOM можно сделать и на чистом JavaScript с помощью его DOM API.

Рассмотрим этот API более подробно:

В конце вы напишете свою простенькую DOM-библиотеку, которую можно будет использовать в любом проекте.

DOM-запросы

DOM-запросы осуществляются с помощью метода.querySelector() , который в качестве аргумента принимает произвольный СSS-селектор.

Const myElement = document.querySelector("#foo > div.bar")

Он вернёт первый подходящий элемент. Можно и наоборот - проверить, соответствует ли элемент селектору:

MyElement.matches("div.bar") === true

Если нужно получить все элементы, соответствующие селектору, используйте следующую конструкцию:

Const myElements = document.querySelectorAll(".bar")

Если же вы знаете, на какой родительский элемент нужно сослаться, можете просто проводить поиск среди его дочерних элементов, вместо того чтобы искать по всему коду:

Const myChildElemet = myElement.querySelector("input") // Вместо: // document.querySelector("#foo > div.bar input")

Возникает вопрос: зачем тогда использовать другие, менее удобные методы вроде.getElementsByTagName() ? Есть маленькая проблема - результат вывода.querySelector() не обновляется, и когда мы добавим новый элемент (смотрите ), он не изменится.

Const elements1 = document.querySelectorAll("div") const elements2 = document.getElementsByTagName("div") const newElement = document.createElement("div") document.body.appendChild(newElement) elements1.length === elements2.length // false

Также querySelectorAll() собирает всё в один список, что делает его не очень эффективным.

Как работать со списками?

Вдобавок ко всему у.querySelectorAll() есть два маленьких нюанса. Вы не можете просто вызывать методы на результаты и ожидать, что они применятся к каждому из них (как вы могли привыкнуть делать это с jQuery). В любом случае нужно будет перебирать все элементы в цикле. Второе - возвращаемый объект является списком элементов, а не массивом. Следовательно, методы массивов не сработают. Конечно, есть методы и для списков, что-то вроде.forEach() , но, увы, они подходят не для всех случаев. Так что лучше преобразовать список в массив:

// Использование Array.from() Array.from(myElements).forEach(doSomethingWithEachElement) // Или прототип массива (до ES6) Array.prototype.forEach.call(myElements, doSomethingWithEachElement) // Проще: .forEach.call(myElements, doSomethingWithEachElement)

У каждого элемента есть некоторые свойства, ссылающиеся на «семью».

MyElement.children myElement.firstElementChild myElement.lastElementChild myElement.previousElementSibling myElement.nextElementSibling

Поскольку интерфейс элемента (Element) унаследован от интерфейса узла (Node), следующие свойства тоже присутствуют:

MyElement.childNodes myElement.firstChild myElement.lastChild myElement.previousSibling myElement.nextSibling myElement.parentNode myElement.parentElement

Первые свойства ссылаются на элемент, а последние (за исключением.parentElement) могут быть списками элементов любого типа. Соответственно, можно проверить и тип элемента:

MyElement.firstChild.nodeType === 3 // этот элемент будет текстовым узлом

Добавление классов и атрибутов

Добавить новый класс очень просто:

MyElement.classList.add("foo") myElement.classList.remove("bar") myElement.classList.toggle("baz")

Добавление свойства для элемента происходит точно так же, как и для любого объекта:

// Получение значения атрибута const value = myElement.value // Установка атрибута в качестве свойства элемента myElement.value = "foo" // Для установки нескольких свойств используйте.Object.assign() Object.assign(myElement, { value: "foo", id: "bar" }) // Удаление атрибута myElement.value = null

Можно использовать методы.getAttibute() , .setAttribute() и.removeAttribute() . Они сразу же поменяют HTML-атрибуты элемента (в отличие от DOM-свойств), что вызовет браузерную перерисовку (вы сможете увидеть все изменения, изучив элемент с помощью инструментов разработчика в браузере). Такие перерисовки не только требуют больше ресурсов, чем установка DOM-свойств, но и могут привести к непредвиденным ошибкам.

Как правило, их используют для элементов, у которых нет соответствующих DOM-свойств, например colspan . Или же если их использование действительно необходимо, например для HTML-свойств при наследовании (смотрите ).

Добавление CSS-стилей

Добавляют их точно так же, как и другие свойства:

MyElement.style.marginLeft = "2em"

Какие-то определённые свойства можно задавать используя.style , но если вы хотите получить значения после некоторых вычислений, то лучше использовать window.getComputedStyle() . Этот метод получает элемент и возвращает CSSStyleDeclaration , содержащий стили как самого элемента, так и его родителя:

Window.getComputedStyle(myElement).getPropertyValue("margin-left")

Изменение DOM

Можно перемещать элементы:

// Добавление element1 как последнего дочернего элемента element2 element1.appendChild(element2) // Вставка element2 как дочернего элемента element1 перед element3 element1.insertBefore(element2, element3)

Если не хочется перемещать, но нужно вставить копию, используем:

// Создание клона const myElementClone = myElement.cloneNode() myParentElement.appendChild(myElementClone)

Метод.cloneNode() принимает булевое значение в качестве аргумента, при true также клонируются и дочерние элементы.

Конечно, вы можете создавать новые элементы:

Const myNewElement = document.createElement("div") const myNewTextNode = document.createTextNode("some text")

А затем вставлять их как было показано выше. Удалить элемент напрямую не получится, но можно сделать это через родительский элемент:

MyParentElement.removeChild(myElement)

Можно обратиться и косвенно:

MyElement.parentNode.removeChild(myElement)

Методы для элементов

У каждого элемента присутствуют такие свойства, как.innerHTML и.textContent , они содержат HTML-код и, соответственно, сам текст. В следующем примере изменяется содержимое элемента:

// Изменяем HTML myElement.innerHTML = `

New content

beep boop beep boop

` // Таким образом содержимое удаляется myElement.innerHTML = null // Добавляем к HTML myElement.innerHTML += ` continue reading...

На самом деле изменение HTML - плохая идея, т. к. теряются все изменения, которые были сделаны ранее, а также перегружаются обработчики событий. Лучше использовать такой способ только полностью отбросив весь HTML и заменив его копией с сервера. Вот так:

Const link = document.createElement("a") const text = document.createTextNode("continue reading...") const hr = document.createElement("hr") link.href = "foo.html" link.appendChild(text) myElement.appendChild(link) myElement.appendChild(hr)

Однако это повлечёт за собой две перерисовки в браузере, в то время как.innerHTML приведёт только к одной. Обойти это можно, если сначала добавить всё в DocumentFragment , а затем добавить нужный вам фрагмент:

Const fragment = document.createDocumentFragment() fragment.appendChild(text) fragment.appendChild(hr) myElement.appendChild(fragment)

Обработчики событий

Один из самых простых обработчиков:

MyElement.onclick = function onclick (event) { console.log(event.type + " got fired") }

Но, как правило, его следует избегать. Здесь.onclick - свойство элемента, и по идее вы можете его изменить, но вы не сможете добавлять другие обработчики используя ещё одну функцию, ссылающуюся на старую.

Для добавления обработчиков лучше использовать.addEventListener() . Он принимает три аргумента: тип события, функцию, которая будет вызываться всякий раз при срабатывании, и объект конфигурации (к нему мы вернёмся позже).

MyElement.addEventListener("click", function (event) { console.log(event.type + " got fired") }) myElement.addEventListener("click", function (event) { console.log(event.type + " got fired again") })

Свойство event.target обращается к элементу, за которым закреплено событие.

А так вы сможете получить доступ ко всем свойствам:

// Свойство `forms` - массив, содержащий ссылки на все формы const myForm = document.forms const myInputElements = myForm.querySelectorAll("input") Array.from(myInputElements).forEach(el => { el.addEventListener("change", function (event) { console.log(event.target.value) }) })

Предотвращение действий по умолчанию

Для этого используется метод.preventDefault() , который блокирует стандартные действия. Например, он заблокирует отправку формы, если авторизация на клиентской стороне не была успешной:

MyForm.addEventListener("submit", function (event) { const name = this.querySelector("#name") if (name.value === "Donald Duck") { alert("You gotta be kidding!") event.preventDefault() } })

Метод.stopPropagation() поможет, если у вас есть определённый обработчик события, закреплённый за дочерним элементом, и второй обработчик того же события, закреплённый за родителем.

Как говорилось ранее, метод.addEventListener() принимает третий необязательный аргумент в виде объекта с конфигурацией. Этот объект должен содержать любые из следующих булевых свойств (по умолчанию все в значении false):

  • capture: событие будет прикреплено к этому элементу перед любым другим элементом ниже в DOM;
  • once: событие может быть закреплено лишь единожды;
  • passive: event.preventDefault() будет игнорироваться (исключение во время ошибки).

Наиболее распространённым свойством является.capture , и оно настолько распространено, что для этого существует краткий способ записи: вместо того чтобы передавать его в объекте конфигурации, просто укажите его значение здесь:

MyElement.addEventListener(type, listener, true)

Обработчики удаляются с помощью метода.removeEventListener() , принимающего два аргумента: тип события и ссылку на обработчик для удаления. Например свойство once можно реализовать так:

MyElement.addEventListener("change", function listener (event) { console.log(event.type + " got triggered on " + this) this.removeEventListener("change", listener) })

Наследование

Допустим, у вас есть элемент и вы хотите добавить обработчик событий для всех его дочерних элементов. Тогда бы вам пришлось прогнать их в цикле, используя метод myForm.querySelectorAll("input") , как было показано выше. Однако вы можете просто добавить элементы в форму и проверить их содержимое с помощью event.target .

MyForm.addEventListener("change", function (event) { const target = event.target if (target.matches("input")) { console.log(target.value) } })

И ещё один плюс данного метода заключается в том, что к новым дочерним элементам обработчик будет привязываться автоматически.

Анимация

Проще всего добавить анимацию используя CSS со свойством transition . Но для большей гибкости (например для игры) лучше подходит JavaScript.

Вызывать метод window.setTimeout() , пока анимация не закончится, - не лучшая идея, так как ваше приложение может зависнуть, особенно на мобильных устройствах. Лучше использовать window.requestAnimationFrame() для сохранения всех изменений до следующей перерисовки. Он принимает функцию в качестве аргумента, которая в свою очередь получает метку времени:

Const start = window.performance.now() const duration = 2000 window.requestAnimationFrame(function fadeIn (now)) { const progress = now - start myElement.style.opacity = progress / duration if (progress < duration) { window.requestAnimationFrame(fadeIn) } }

Таким способом достигается очень плавная анимация. В своей статье Марк Браун рассуждает на данную тему.

Пишем свою библиотеку

Тот факт, что в DOM для выполнения каких-либо операций с элементами всё время приходится перебирать их, может показаться весьма утомительным по сравнению с синтаксисом jQuery $(".foo").css({color: "red"}) . Но почему бы не написать несколько своих методов, облегчающую данную задачу?

Const $ = function $ (selector, context = document) { const elements = Array.from(context.querySelectorAll(selector)) return { elements, html (newHtml) { this.elements.forEach(element => { element.innerHTML = newHtml }) return this }, css (newCss) { this.elements.forEach(element => { Object.assign(element.style, newCss) }) return this }, on (event, handler, options) { this.elements.forEach(element => { element.addEventListener(event, handler, options) }) return this } } }

Сложные и тяжелые веб-приложения стали обычными в наши дни. Кроссбраузерные и простые в использовании библиотеки типа jQuery с их широким функционалом могут сильно помочь в манипулировании DOM на лету. Поэтому неудивительно, что многие разработчики использую подобные библиотеки чаще, чем работают с нативным DOM API, с которым было немало проблем . И хотя различия в браузерах по-прежнему остаются проблемой, DOM находится сейчас в лучшей форме, чем 5-6 лет назад , когда jQuery набирал популярность.

В этой статье я продемонстрирую возможности DOM по манипулированию HTML, сфокусировавшись на отношения родительских, дочерних и соседних элементов. В заключении я дам данные о поддержке этих возможностей в браузерах, но учитывайте, что библиотека типа jQuery по-прежнему остается хорошей опцией в силу наличия багов и непоследовательностей в реализации нативного функционала.

Подсчет дочерних узлов

Для демонстрации я буду использовать следующую разметку HTML, в течение статьи мы ее несколько раз изменим:

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Var myList = document.getElementById("myList"); console.log(myList.children.length); // 6 console.log(myList.childElementCount); // 6

Как видите, результаты одинаковые, хотя техники используются разные. В первом случае я использую свойство children . Это свойство только для чтения, оно возвращает коллекцию элементов HTML, находящихся внутри запрашиваемого элемента; для подсчета их количества я использую свойство length этой коллекции.

Во втором примере я использую метод childElementCount , который мне кажется более аккуратным и потенциально более поддерживаемым способом (подробнее обсудим это позже, я не думаю, что у вас возникнут проблемы с пониманием того, что он делает).

Я мог бы попытаться использовать childNodes.length (вместо children.length), но посмотрите на результат:

Var myList = document.getElementById("myList"); console.log(myList.childNodes.length); // 13

Он возвращает 13, потому что childNodes это коллекция всех узлов, включая пробелы - учитывайте это, если вам важна разница между дочерними узлами и дочерними узлами-элементами.

Проверка существования дочерних узлов

Для проверки наличия у элемента дочерних узлов я могу использовать метод hasChildNodes() . Метод возвращает логическое значение, сообщающие об их наличии или отсутствии:

Var myList = document.getElementById("myList"); console.log(myList.hasChildNodes()); // true

Я знаю, что в моем списке есть дочерние узлы, но я могу изменить HTML так, чтобы их не было; теперь разметка выглядит так:

И вот результат нового запуска hasChildNodes() :

Console.log(myList.hasChildNodes()); // true

Метод по прежнему возвращает true . Хотя список не содержит никаких элементов, в нем есть пробел, являющийся валидным типом узла. Данный метод учитывает все узлы, не только узлы-элементы. Чтобы hasChildNodes() вернул false нам надо еще раз изменить разметку:

И теперь в консоль выводится ожидаемый результат:

Console.log(myList.hasChildNodes()); // false

Конечно, если я знаю, что могу столкнуться с пробелом, то сначала я проверю существование дочерних узлов, затем с помощью свойства nodeType определяю, есть ли среди них узлы-элементы.

Добавление и удаление дочерних элементов

Есть техника, которые можно использовать для добавления и удаления элементов из DOM. Наиболее известная из них основана на сочетании методов createElement() и appendChild() .

Var myEl = document.createElement("div"); document.body.appendChild(myEl);

В данном случае я создаю

с помощью метода createElement() и затем добавляю его к body . Очень просто и вы наверняка использовали эту технику раньше.

Но вместо вставки специально создаваемого элемента, я также могу использовать appendChild() и просто переместить существующий элемент. Предположим, у нас следующая разметка:

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Example text

Я могу изменить место расположения списка с помощью следующего кода:

Var myList = document.getElementById("myList"), container = document.getElementById("c"); container.appendChild(myList);

Итоговый DOM будет выглядеть следующим образом:

Example text

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Обратите внимание, что весь список был удален со своего места (над параграфом) и затем вставлен после него перед закрывающим body . И хотя обычно метод appendChild() используется для добавления элементов созданных с помощью createElement() , он также может использоваться для перемещения существующих элементов.

Я также могу полностью удалить дочерний элемент из DOM с помощью removeChild() . Вот как удаляется наш список из предыдущего примера:

Var myList = document.getElementById("myList"), container = document.getElementById("c"); container.removeChild(myList);

Теперь элемент удален. Метод removeChild() возвращает удаленный элемент и я могу его сохранить на случай, если он потребуется мне позже.

Var myOldChild = document.body.removeChild(myList); document.body.appendChild(myOldChild);

Таке существует метод ChildNode.remove() , относительно недавно добавленный в спецификацию:

Var myList = document.getElementById("myList"); myList.remove();

Этот метод не возвращает удаленный объект и не работает в IE (только в Edge). И оба метода удаляют текстовые узлы точно так же, как и узлы-элементы.

Замена дочерних элементов

Я могу заменить существующий дочерний элемент новым, независимо от того, существует ли этот новый элемент или я создал его с нуля. Вот разметка:

Example Text

Var myPar = document.getElementById("par"), myDiv = document.createElement("div"); myDiv.className = "example"; myDiv.appendChild(document.createTextNode("New element text")); document.body.replaceChild(myDiv, myPar);

New element text

Как видите, метод replaceChild() принимает два аргумента: новый элемент и заменяемый им старый элемент.

Я также могу использовать это метод для перемещения существующего элемента. Взгляните на следующий HTML:

Example text 1

Example text 2

Example text 3

Я могу заменить третий параграф первым параграфом с помощью следующего кода:

Var myPar1 = document.getElementById("par1"), myPar3 = document.getElementById("par3"); document.body.replaceChild(myPar1, myPar3);

Теперь сгенерированный DOM выглядит так:

Example text 2

Example text 1

Выборка конкретных дочерних элементов

Существует несколько разных способов выбора конкретного элемента. Как показано ранее, я могу начать с использования коллекции children или свойства childNodes . Но взглянем на другие варианты:

Свойства firstElementChild и lastElementChild делают именно то, чего от них можно ожидать по их названию: выбирают первый и последний дочерние элементы. Вернемся к нашей разметке:

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Я могу выбрать первый и последний элементы с помощью этих свойств:

Var myList = document.getElementById("myList"); console.log(myList.firstElementChild.innerHTML); // "Example one" console.log(myList.lastElementChild.innerHTML); // "Example six"

Я также могу использовать свойства previousElementSibling и nextElementSibling , если я хочу выбрать дочерние элементы, отличные от первого или последнего. Это делается сочетанием свойств firstElementChild и lastElementChild:

Var myList = document.getElementById("myList"); console.log(myList.firstElementChild.nextElementSibling.innerHTML); // "Example two" console.log(myList.lastElementChild.previousElementSibling.innerHTML); // "Example five"

Также есть сходные свойства firstChild , lastChild , previousSibling , и nextSibling , но они учитывают все типы узлов, а не только элементы. Как правило, свойства, учитывающие только узлы-элементы полезнее тех, которые выбирают все узлы.

Вставка контента в DOM

Я уже рассматривал способы вставки элементов в DOM. Давайте перейдем к похожей теме и взглянем на новые возможности по вставке контента.

Во-первых, есть простой метод insertBefore() , он во многом похож на replaceChild() , принимает два аргумента и при этом работает как с новыми элементами, так и с существующими. Вот разметка:

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Example Paragraph

Обратите внимание на параграф, я собираюсь сначала убрать его, а затем вставить перед списком, все одним махом:

Var myList = document.getElementById("myList"), container = document.getElementBy("c"), myPar = document.getElementById("par"); container.insertBefore(myPar, myList);

В полученном HTML параграф будет перед списком и это еще один способ перенести элемент.

Example Paragraph

  • Example one
  • Example two
  • Example three
  • Example four
  • Example five
  • Example Six

Как и replaceChild() , insertBefore() принимает два аргумента: добавляемый элемент и элемент, перед которым мы хотим его вставить.

Этот метод прост. Попробуем теперь более мощный способ вставки: метод insertAdjacentHTML() .

Многие наверняка сталкивались с проблемой, когда какой-нибудь текст нужно выводить в одну строку. При этом текст может быть весьма длинным, а ширина блока, в котором этот текст находится, обычно ограничена, хотя бы тем же размером окна браузера. На эти случаи придумано свойство text-overflow , которое внесено в рекомендацию CSS3 , а впервые было реализовано в IE6, очень давно. В случае использования этого свойства для блока, если его текст больше по ширине чем сам блок, то текст обрезается и в конце ставится многоточие. Хотя тут не все так просто, но вернемся к этому чуть позже.
С Internet Explorer"ом все понятно, что же относительно других браузеров? И хотя в настоящий момент из спецификации CSS3 свойство text-overflow исключено, Safari его поддерживает (по крайней мере, в 3-й версии), Opera тоже (с 9-й версии, правда называется свойство -o-overflow-text). А Firefox - нет, не поддерживает, и даже в 3-й версии не будет. Печально, но факт. Но может можно что-то сделать?

Сделать, конечно, можно. Когда искал в интернете по поводу этого свойства, и как с этим в Firefox, наткнулся на с простым решением. Суть решения:

Вот и все. Детали читайте в статье.
Решение не плохое, но есть проблемы:

  1. Текст может обрезаться посередине (условно говоря) буквы, и мы увидим ее «обрубок».
  2. Многоточие отображается всегда, даже когда текст меньше ширины блока (то есть не выпадает из него и многоточие не нужно).

Шаг первый

Для начала сосредоточимся на второй проблеме, а именно, как избежать отображения многоточия когда это не нужно. Поломав голову и «немного» поэкспериментировав, нашел некоторое решение. Попробую объяснить.
Суть в том, что нам нужен отдельный блок с многоточием, который будет появляться только тогда, когда текст занимает слишком много пространства по ширине. Тут мне пришла идея о сваливающемся плавающем блоке. Хоть и звучит страшно, но тут, всего лишь, имеется ввиду блок, который есть всегда, и прижат вправо, но когда ширина текста становится большой, текст выталкивает блок на следующую линию.
Перейдем к практике, иначе сложно объяснить. Зададим HTML структуру:

very long text

Не очень компактно, но меньшего у меня не получилось. Итак, мы имеем блок-контейнер DIV.ellipsis, блок с нашим текстом и еще один блок, который будет содержать многоточие. Заметим, что «блок с многоточием» на самом деле пустой, ведь нам не нужны лишние три точки, когда мы будем копировать текст. Так же не стоит пугаться отсутствия дополнительных классов, так как данная структура хорошо адресуется посредством CSS селекторов. А вот и сам CSS:
.ellipsis { overflow: hidden; white-space: nowrap; line-height: 1.2em; height: 1.2em; border: 1px solid red; } .ellipsis > DIV:first-child { float: left; } .ellipsis > DIV + DIV { float: right; margin-top: -1.2em; } .ellipsis >

Вот и все. Проверяем и убеждаемся что в Firefox, Opera, Safari работает как и задумано. В IE весьма странный, но предсказуемый, результат. В IE6 все разъехалось, а в IE7 просто не работает, так как он не поддерживает генерируемый контент. Но к IE мы еще вернемся.

Пока же разберем сделанное. Во-первых, мы задаем line-height и height основного блока, так как нам нужно знать высоту блока и высоту текстовой линии. Это же значение мы задаем для margin-top блока с многоточием, но с отрицательным значением. Таким образом, когда блок не «сброшен» на следующую линию, то будет выше строки текста (на одну линию), когда сбросится - будет на ее уровне (на самом деле он ниже, просто мы делаем оттяжку на одну линию вверх). Что бы скрыть лишнее, особенно когда не нужно показывать многоточие, мы делаем overflow: hidden для основного блока, таким образом, когда многоточие будет выше линии - оно не будет показываться. Это же позволяет нам убрать и, выпадающий за пределы блока (в правый край), текст. Чтобы текст неожиданно не переносился и не выталкивал блок с многоточием все ниже и ниже, мы делаем white-space: nowrap, тем самым запрещая переносы - наш текст будет всегда в одну строку. Для блока с текстом мы поставили float: left, чтобы он сразу же не сваливал блок с многоточием и занимал минимальную ширину. Так как внутри основного блока (DIV.ellipsis) оба блока плавающие (float: left/right), то основной блок схлопнется, когда блок с текстом будет пустой, поэтому для основного блока мы выставили фиксированную высоту (height: 1.2em). Ну и последнее, используем псевдо-элемент::after для отображения многоточия. Для этого псевдо-элемента так же задаем фон, чтобы перекрыть текст который окажется под ним. Мы задали рамку для основного блока, только для того чтобы увидеть габариты блока, позже мы ее уберем.
Если бы Firefox, так же хорошо поддерживал псевдо-элементы, как Opera и Safari, в плане их позиционирования (задания для них position/float etc), то можно было бы не использовать отдельный блок для многоточия. Попробуйте заменить последние 3 правила, на следующий:

.ellipsis > DIV:first-child::after { float: right; content: "..."; margin-top: -1.2em; background-color: white; position: relative; }

в Opera и Safari, все работает как прежде, и без дополнительного блока с многоточием. А вот Firefox разочаровывает. А ведь именно для него мы делаем решение. Что ж - придется обходиться изначальной HTML структурой.

Шаг второй

Как вы могли заметить, мы избавились от проблемы появления многоточия, когда текст умещается в блок. Однако, у нас осталась еще одна проблема - текст обрезается посередине букв. И к тому же в IE это не работает. Чтобы побороть и то и другое, нужно использовать родное правило text-overflow для браузеров, и только для Firefox использовать описанное выше решение (альтернативы нет). Как сделать решение «только для Firefox» разберемся позже, а сейчас попробуем заставить работать то что есть с использованием text-overflow. Подправим CSS:

.ellipsis { overflow: hidden; white-space: nowrap; line-height: 1.2em; height: 1.2em; border: 1px solid red; text-overflow: ellipsis; -o-text-overflow: ellipsis; width: 100%; } .ellipsis * { display: inline; } /* .ellipsis > DIV:first-child { float: left; } .ellipsis > DIV + DIV { float: right; margin-top: -1.2em; } .ellipsis > DIV + DIV::after { background-color: white; content: "..."; } */

Править, как оказалось, не много. В стиль основного блока добавилось три строчки. Две из них включают text-overflow. Задание ширины width: 100% нужно для IE, чтобы текст не раздвигал блок до бесконечности, и свойство text-overflow сработало; по сути, мы ограничили ширину. По идее DIV, как и все блочные элементы, растягивается по всей ширине контейнера, и width: 100% ни к чему, и даже вредно, но у IE проблема с layout, так как контейнер всегда растягивается по размерам контента, поэтому иначе нельзя. Так же мы сделали все внутренние элементы строковыми (inline), потому как для некоторых браузеров (Safari & Opera) text-overflow иначе не сработает, так как внутри есть блочные (block) элементы. Мы закомментировали три последних правила, так как в данном случае они не нужны и все ломают (конфликтуют). Данные правила нужны будут только для Firefox.
Посмотрим что у нас получилось и продолжим.

Шаг третий

Когда я в очередной раз заглянул на страничку (перед тем как собирался писать эту статью), упоминаемую в самом начале, то, интереса ради, проглядел комментарии, на предмет умных смежных идей. И нашел , в комментарии, где говорилось о другом решении, которое работает в Firefox и IE (для этого человека, как и для автора первой статьи, других браузеров, видимо, не существует). Так вот, в этом решении, автор несколько иначе борется с данным явлением (отсутствием text-overflow), навешивая обработчики на события overflow и underflow элементам, для которых нужно было ставить многоточие при необходимости. Не плохо, но мне кажется это решение очень дорогое (в плане ресурсов), тем более что оно у него несколько подглючивает. Однако, разбираясь, как он этого добился, наткнулся на интересную штуку, а именно CSS свойство -moz-binding. Насколько я понял, это аналог behaviour в IE, только под другим соусом и покруче. Но не будем углубляться в детали, скажем только, что таким способом можно повесить JavaScript обработчик на элемент с помощью CSS. Звучит странно, но это работает. Что мы делаем:

.ellipsis { overflow: hidden; white-space: nowrap; line-height: 1.2em; height: 1.2em; border: 1px solid red; text-overflow: ellipsis; -o-text-overflow: ellipsis; width: 100%; -moz-binding: url(moz_fix.xml#ellipsis); zoom: 1; } .ellipsis * { display: inline; } .moz-ellipsis > DIV:first-child { float: left; display: block; } .moz-ellipsis > DIV + DIV { float: right; margin-top: -1.2em; display: block; } .moz-ellipsis > DIV + DIV::after { background-color: white; content: "..."; }

Как видно мы опять внесли не много изменений. На этом шаге в IE7 наблюдается странный глюк, все перекашивается, если не поставить zoom: 1 для основного блока (самый простой вариант). Если убрать (удалить, закомментировать) правило.ellipsis * или.moz-ellipsis > DIV + DIV (которое вообще никак не касается IE7), то глюк пропадает. Странно все это, если кто знает в чем дело, дайте знать. Пока же обойдемся zoom: 1 и перейдем к Firefox.
Свойство -moz-binding подключает файл moz_fix.xml инструкцию с идентификатором ellipsis. Содержимое этого xml файла следующее:

Все что делает данный constructor, это к элементу, для которого сработал селектор, добавляет класс moz-ellipsis. Это будет работать только в Firefox (gecko браузерах?), поэтому только в нем к элементам будет добавлен класс moz-ellipsis, и мы можем для этого класса дописать дополнительные правила. Чего и добивались. Не совсем уверен относительно необходимости this.style.mozBinding = "", но по опыту с expression лучше перестраховаться (вообще я слабо знаком с этой стороной Firefox, потому могу заблуждаться).
Вас может насторожить, что в данном приеме используется внешний файл и вообще JavaScript. Пугаться не стоит. Во первых если файл не подгрузится и/или JavaScript отключен и не сработает, ничего страшного, пользователь просто не увидит многоточия в конце, текст будет обрезаться по окончанию блока. То есть в данном случае получаем «unobtrusive» решение. Можете сами убедиться .

Таким образом, мы получили стиль для браузеров «большой четверки», который реализует text-overflow для Opera, Safari & IE, а для Firefox его эмулирует, не ахти как, но это лучше чем ничего.

Шаг четвертый

На этом месте можно было бы поставить точку, но хотелось бы немного улучшить наше решение. Раз мы можем повесить constructor на любой блок и соответственно получаем над ним контроль, почему бы не воспользоваться этим. Упростим нашу структуру:

very long text

О, да! Думаю, вы со мной согласитесь - это то что надо!
Теперь уберем из стиля все лишнее:
.ellipsis { overflow: hidden; white-space: nowrap; line-height: 1.2em; height: 1.2em; text-overflow: ellipsis; -o-text-overflow: ellipsis; width: 100%; -moz-binding: url(moz_fix.xml#ellipsis); } .moz-ellipsis > DIV:first-child { float: left; } .moz-ellipsis > DIV + DIV { float: right; margin-top: -1.2em; } .moz-ellipsis > DIV + DIV::after { background-color: white; content: "..."; }

Мы наконец-то убрали красную рамку:)
А теперь, немного допишем наш moz_fix.xml:

Что тут происходит? Мы воссоздаем нашу начальную HTML структуру. То есть те сложности с блоками делаются автоматически, и только в Firefox. JavaScript код написан в лучших традициях:)
К сожалению, ситуацию, когда текст обрезается посередине буквы, мы избежать не можем (правда, возможно, временно, так как мое такое решение пока еще очень сырое, и в будущем может получится). Но можем немного сгладить этот эффект. Для этого нам понадобится изображение (белый фон с прозрачным градиентом), и немного изменений в стиль:
.moz-ellipsis > DIV:first-child { float: left; margin-right: -26px; } .moz-ellipsis > DIV + DIV { float: right; margin-top: -1.2em; background: url(ellipsis.png) repeat-y; padding-left: 26px; }

Смотрим и радуемся жизни.

На этом и поставим точку.

Заключение

Приведу небольшой пример , для сторонней верстки. Я взял оглавление одной из страниц Wikipedia (первое что подвернулось), и применил для него описанный выше метод.
Вообще же данное решение можно назвать универсальным лишь с натяжкой. Все зависит от вашей верстки и ее сложности. Возможно, понадобится напильник, а может и бубен. Хотя в большинстве случаев, я думаю, работать будет. И потом, у вас теперь есть отправная точка;)
Надеюсь, вы почерпнули из статьи что-то интересное и полезное;) Учитесь, экспериментируйте, делитесь.
Удачи!

Как же порой раздражают длинные названия ссылок товаров, - по три строчки, - или длинных заголовков иных блоков. Как было бы здорово, если бы можно было все это дело как-то приужать, сделать компактнее. А при наведении мышкой уже показывать title полностью.

Для этого нам на помощь придет наш любимый CSS. Все очень просто, смотрите.

Допустим, у нас есть вот такой блок из каталога с товарами:

Вот его структура:

Чудо-юдо силопридаватель вечерний, таинственный, арт. 20255-59

Замечательный товар по супер-цене, всего за 100 руб. Скрасит ваши одинокие вечера и даст прилив жизненных сил!

Вот его стили:

Someblock{ border: 1px solid #cccccc; margin: 15px auto; padding: 10px; width: 250px; } .header{ border-bottom: 1px dashed; font-size: 16px; font-weight: bold; margin-bottom: 12px; }

Согласитесь, выглядит ужасно. Конечно, можно сократить название товара. Но что делать, если таких сотни или тысячи? Можно также средствами php обрезать часть названия, ограничившись каким-то количеством символов. Но что делать, если потом поменяется верстка и блоки будут меньше или наоборот раза в 2-3 больше? Все это не вариант, все это нам не подходит.

И тут нам на помощь приходит CSS и его волшеблое свойство text-overflow . Но его нужно правильно использовать совместно с несколькими другими свойствами. Ниже я покажу вам готовое решение, после чего объясню что к чему.

Итак, отодвинув в сторону ручное редактирование названий товаров и программную обрезку стилей, мы берем в руки CSS и смотрим, что у нас получится:

Чудо-юдо силопридаватель вечерний, таинственный, арт. 20255-59

Замечательный товар по супер-цене, всего за 100 руб. Скрасит ваши одинокие вечера и даст прилив жизненных сил!

Ну как, лучше? По-моему очень даже! А поднесите мышку к названию... вуаля! Вот оно нам показывается полностью.

В структуре у нас ничего не поменялось, я лишь добавил диву с классом .header тег title. А вот новые, дополненные стили:

Someblock{ border: 1px solid #cccccc; margin: 15px auto; padding: 10px; width: 250px; } .header{ border-bottom: 1px dashed; font-size: 16px; font-weight: bold; margin-bottom: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

Смотрите, что мы сделали:

  • Мы добавили блоку свойство white-space: nowrap , которое убирает у текста возможность переноса слов на новую строку, тем самым вытягивая его в линию;
  • Затем мы добавили свойство overflow: hidden , которое ограничило видимость нашей строки шириной блока, тем самым обрезая все лишнее и не показывая его посетителю;
  • Ну и в конце мы добавили троеточие к нашей строке посредствам свойства text-overflow: ellipsis , тем самым давая посетителю понять, что это не конец строки, и что нужно, наверное, поднести мыщку и посмотреть ее полностью.

Любите CSS, изучайте CSS, и сайтостроительство не покажется вам такой уж сложной вещью =)


Есть текст произвольной длины, который нужно вывести внутри блока фиксированной высоты и ширины. При этом, если текст помещается не полностью, отображаться должен фрагмент текста, который полностью помещается в заданный блок, после чего устанавливается многоточие.

Такая задача является довольно распространенной, в то же время она есть не такой тривиальной, как кажется.

Вариант для однострочного текста на CSS

В этом случае можно использовать свойство text-overflow: ellipsis . При этом контейнер должен иметь свойство overflow равное hidden или clip

Block { width : 250px ; white-space : nowrap ; overflow : hidden ; text-overflow : ellipsis ; }

Вариант для многострочного текста на CSS

Первый способ обрезать многострочный текст с использованием CSS свойств применить псевдо-элементы :before и :after . Для начала HTML-разметка

Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

А теперь и сами свойства

Box { overflow : hidden ; height : 200px ; width : 300px ; line-height : 25px ; } .box :before { content : "" ; float : left ; width : 5px ; height : 200px ; } .box > * :first-child { float : right ; width : 100% ; margin-left : -5px ; } .box :after { content : "\02026" ; box-sizing : content-box ; float : right ; position : relative ; top : -25px ; left : 100% ; width : 3em ; margin-left : -3em ; padding-right : 5px ; text-align : right ; background-size : 100% 100% ; background : linear-gradient (to right , rgba (255 , 255 , 255 , 0 ), white 50% , white ); }

Еще один способ - использовать свойство column-width с помощью которого задаем ширину колонки для многострочного текста. Правда, при использовании этого способа установить в конце многоточие неполучится. HTML:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

Block { overflow : hidden ; height : 200px ; width : 300px ; } .block__inner { -webkit-column-width : 150px ; -moz-column-width : 150px ; column-width : 150px ; height : 100% ; }

Третий способ решение для многострочного текста на CSS есть для браузеров Webkit . В нем нам прийдется использовать сразу несколько специфичных свойств с префиксом -webkit . Основным является -webkit-line-clamp которое позволяет указать количество выводимых в блоке строк. Решение красивое но довольно ограниченное за счет своей работы в ограниченной группе браузеров

Block { overflow : hidden ; text-overflow : ellipsis ; display : -webkit-box ; -webkit-line-clamp : 2 ; -webkit-box-orient : vertical ; }

Вариант для многострочного текста на JavaScript

Тут используется дополнительный невидимый блок, в который изначально помещается наш текст, после чего, удаляется по одному символу, пока высота этого блока не станет меньше либо равной высоте нужного блока. И в конце текст перемещается в исходный блок.

var block = document . querySelector (".block" ), text = block . innerHTML , clone = document . createElement ("div" ); clone . style . position = "absolute" ; clone . style . visibility = "hidden" ; clone . style . width = block . clientWidth + "px" ; clone . innerHTML = text ; document . body . appendChild (clone ); var l = text . length - 1 ; for (; l >= 0 && clone . clientHeight > block . clientHeight ; -- l ) { clone . innerHTML = text . substring (0 , l ) + "..." ; } block . innerHTML = clone . innerHTML ;

Это же в виде плагина для jQuery:

(function ($ ) { var truncate = function (el ) { var text = el . text (), height = el . height (), clone = el . clone (); clone . css ({ position : "absolute" , visibility : "hidden" , height : "auto" }); el . after (clone ); var l = text . length - 1 ; for (; l >= 0 && clone . height () > height ; -- l ) { clone . text (text . substring (0 , l ) + "..." ); } el . text (clone . text ()); clone . remove (); }; $ . fn . truncateText = function () { return this . each (function () { truncate ($ (this )); }); }; }(jQuery ));