• CSS-свойство content: копировать или нет?

    Решил я для очередного своего проекта воспользоваться модным CSS-свойством content, чтобы немного облегчить страницу и сделать настройку внешнего вида более гибкой. Так как проект ориентирован на веб-разработчиков, об обратной совместимости со старыми браузерами (IE6—7) можно было не беспокоится. Но, к сожалению, меня ожидало большое разочарование от использования этого свойства. Нет, всё отображалось правильно, но конечному пользователю было бы неудобно пользоваться результатом.

    Что такое CSS-свойство content

    Кому ещё не удалось познакомиться с этим замечательным свойством, кратко расскажу о нём. Само название этого свойства говорит о том, что оно управляет неким содержимым. Согласно спецификации CSS2 это свойство применяется только к псевдо-элементам :before и :after, а с версии CSS3 станет доступно и для обычных элементов (небольшой реверанс в сторону Opera, которая это уже поддерживает).

    С помощью свойства content мы можем через CSS задавать текстовое содержимое для (псевдо-)элементов. Классический пример применения этого свойства — вывод содержимого ссылки рядом с элементом в версии сайта для печати:

    <style type="text/css">
    	a:after {
    		content: ' (' attr(href) ')';
    	}
    </style>
    <p>В нашем <a href="/catalog/">каталоге</a> вы найдёте много чего интересного.</p>
    

    С помощью псевдо-элемента :after мы задали некое содержимое после тэга <a>. Этим содержимым является результат конкатенации строк и функции attr(), которая выводит содержимое атрибута контекстного элемента. Браузер, полностью поддерживающий CSS2, изобразит этот код примерно так:

    content-example

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

    Проблема

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

    Проект, который я сейчас делаю, является инструментом для удобной работы с XML-документами. Для вывода раскрашенного дерева элементов я и решил воспользоваться вышеозначенным свойством:

    <style type="text/css">
    	.tag:before {
    		content:'<';
    	}
    
    	.tag:after {
    		content:'>';
    	}
    
    	.attr-value {
    		quotes:'"' '"';
    	}
    
    	.attr-value:before {
    		content:open-quote;
    	}
    
    	.attr-value:after {
    		content:close-quote;
    	}
    </style>
    <div class="tag">div <span class="attr-name">class</span>=<q class="attr-value">demo</q></div>
    

    Плюсы такого подхода: потребуется гораздо меньше элементов для раскраски тэгов (через псевдо-элементы :before и :after я могу задать произвольный цвет у угловых скобок). Для значений атрибутов я воспользовался тэгом <q> и CSS-свойством quotes, через которое определяются открывающие и закрывающие кавычки. Кому-то нравится использовать двойные кавычки в коде, кому-то — ординарные, при таком подходе их можно на лету поменять у всего документа. Как оказалось, выбор этого тэга и CSS-свойства стал важной частью эксперимента.

    В браузерах этот код выглядит замечательно:

    content-example2

    Но XML-документ должен не только красиво выглядеть, но и правильно работать: пользователь имеет право без проблем выделить и скопировать фрагмента документа, чтобы воспользоваться им, например, в своём любимом редакторе. И тут меня ожидало полное разочарование от использования свойства content. Я проверил результат копирования в своих браузерах — Safari 4, Opera 10, Firefox 3.5, IE8 — и получил вот такой результат:

    • Safari: div class=demo
    • Opera: <div class="demo">
    • Firefox: div class="demo"
    • IE8: <div class="demo">

    Как видите, все скопировали текст по-разному: Safari не скопировал content-данные в принципе, Opera и IE8 скопировали всё, а Firefox скопировал только кавычки вокруг атрибута.

    Затем я решил вместо вместо элемента <q> написать обычный <span>, и получил вот такой результат:

    • Safari: div class=demo
    • Opera: <div class="demo">
    • Firefox: div class=demo
    • IE8: <div class="demo">

    Всё то же самое, но Firefox уже не скопировал кавычки.

    Выводы

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

    • Safari в принципе не понимает CSS-свойство quotes. То, что браузер отобразил кавычки вокруг <q> элемента — исключительно стандартная реакция на него. Кавычки нельзя будет поменять через свойство quotes, например, на ординарные — они так и останутся двойными. А если элемент переименовать в <span>, то и вовсе пропадут.
    • Firefox при копировании текста обращает внимание на название элемента: если это <q> — кавычки скопируются, для другого элемента получите пустоту.
    • Firefox всегда копирует двойные кавычки для тэга <q>, даже если вы измените их на что-нибудь другое (на «ёлочки», например). То есть сделать трюк с управлением копирования символов у вас не получится. Либо двойные кавычки, либо ничего.
    • IE8 при копировании обращает внимание на тип элемента: например, если прописать тэгу display: list-item, то скопируется буллит (хотя на странице он не будет отображаться).

    В общем, выводы далеко не самые приятные. С помощью свойства content я не смогу сделать кроссбраузерное решение: и когда нужно копировать эти данные, и когда не нужно (например, нумерация строк в листинге кода). Как это часто бывает, радостные вопли неискушённых кодеров и красивые демонстрации маркетологов ломаются в момент «боевого» применения, когда нужно вкладывать смысл в свою работу, а не просто следовать модным тенденциям. Поэтому до сих пор приходится делать всё по-старинке.

  • 19 комментариев

    1. Егор
      15 января 2010

      Эх. Еще одни грабли. Ну спасибо, будем знать.

    2. Некит
      15 января 2010

      По старинке это как?)

    3. 16 января 2010

      Сочувствую, день работы практически впустую. Спасибо, что осветил тему.

    4. 16 января 2010

      С помощью свойства content мы может через CSS

    5. 16 января 2010

      «С помощью псевдо-элемента :before мы задали некое содержимое после тэга » :after, конечно.
      Спасибо, про браузерный расклад, в копилку.

    6. 16 января 2010

      Вопрос: а зачем тебе пользоваться open-quote / close-quote, если можно эмулировать это поведение при помощи обычных :before / :after? Вот, к примеру, решение с кавычками для кириллицы:

      Q:before { content:’0AB’; }
      Q Q:before { content:’201E’; }
      Q:after { content:’0BB’; }
      Q Q:after { content:’201C’; }

      Это раз. Два — если ты построил очень сложный и специфический пример, то это не значит, что невозможно сделать кроссбраузерное решение чего-либо. Так что «вопли неискушённых» и «красивые демонстрации» попахивают популизмом, мол, всё фигня — ничего не работает. Работает, если с умом и в определённом круге задач.

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

    7. 16 января 2010

      Интересно, что глючнее всего опенсурсные браузеры. Теоретически, можно их исправить, направив патчи.

    8. Сергей Чикуенок
      16 января 2010

      Вопрос: а зачем тебе пользоваться open-quote / close-quote, если можно эмулировать это поведение при помощи обычных :before / :after

      Суть примера не в «а что же делать?», а в том, чтобы показать неоднозначность работы этого свойства в том же Firefox.

      Работает, если с умом и в определённом круге задач.

      Хорошо, тогда приведи 3 примера «правильного» с точки зрения спецификации использования свойства content.

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

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

    9. 17 января 2010

      ого, интересно послушать ответ pepelsbey =)

    10. 17 января 2010

      https://bugs.webkit.org/show_bug.cgi?id=3234

      Для Firefox’а баг не нашёл, хотя он наверняка есть на https://bugzilla.mozilla.org/

    11. 17 января 2010

      Да тут речь не о «правильности» использования этого свойства, а о том, что его, в принципе, можно использовать. Главное не требовать слишком много от сравнительно молодого механизма и не думать, что договориться о единой спеке это так просто 😉 Ах, да — ещё можно регулярно слать багрепорты.

      Примеры?

      1. Кавычки для , к сожалению, через ж. для Webkit’а, но всё же
      2. Автоматическая нумерация списков/заголовков. Для списков главной профит в управляемости (раскрасить и расположить циферки и буллиты)
      3. Хорошая замена для экстра-разметки, например для image-replacement’а, когда эта штука позиционируется и закрывает оригинальный текст блоком с (фоновой) картинкой.

      Всё это эмулируется для IE7 и младше при помощи JS или expression’ов — зависит от ситуации.

      ps: ты каким плагином/решением пользуешься для подписки на комментарии?

    12. 17 января 2010

      Вот если рассмотреть нумерацию списков и заголовков — разве номер это не часть содержимого заголовка, разве при копировании можно его просто отбросить?
      Вообще, если подумать, то должны ли копироваться сгенерированные элементы?
      Сгенерированный текст может нести исключительно декоративную функцию, как, например, нумерация строк в листингах (и при этом копироваться не должен), а может иметь и смысловую нагрузку — как при нумерации заголовков, и в этом случае он должен копироваться.
      Несмотря на название свойства «content», оно определяется в таблице стилей, что говорит о его декоративном назначении, то есть в первую очередь для оформления. В этом случае его можно «с чистой совестью» не копировать. Однако в большинстве практических задач хотелось бы генерировать с помощью этого подхода полноценный текст.
      Получается, что нужен способ управлять ролью сгенерированного содержимого (оформление или контент)?

    13. 17 января 2010

      [offtop]
      Насчёт горячих клавиш Zen Coding’а для форм:

      Cmd+Shift+A — автозаполнение
      Cmd+L — фокус в адресной строке
      Cmd+D — добавить закладку
      Cmd+Shift+D — молча добавить закладку

      …в Safari. Так что дохлый номер 🙂

      Насчёт плагина для подписки — я уже понял какой, по HTML-коду 😉
      [/offtop]

    14. 17 января 2010

      Мне кажется, что принцип разделения структуры, оформления и поведения уже изначально говорит о том, что все в CSS должно нести сугубо оформительскую функцию. Так что не стоит требовать от браузера копирования «рюшек».

      Именно по этой причине я считаю ошибочным появление в CSS таких вещей, как анимация.

    15. Михаил
      18 января 2010

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

    16. Ехидный_Роман
      18 января 2010

      Один из немногих блогов-сайтов о верстке на котором можно поучится реальным вещам. 99% ресурсов — это идиотические пубертатные посты типа «как сделать восмигранные углы с помощью CSS23/HTML25» и/или их перепечатка.
      Респект что находите время для постов!

    17. Дима
      15 ноября 2010

      Хочу с помощью .className:before {content:»»} сделать так, чтобы в начале содержимого элемента добавлялось например слово «Пример:» и сброс на новую строку. Пробовал добавлять символ ентера 2386 — не получается, квадратик вместо него дает, думал что-то с коджировкой, но страница в юникоде, и чаркод тоже юникодный.. подскажите как правильно это сделать, может быть можно как то просто вставить тег после слова?

    18. Дима
      15 ноября 2010

      Получилось только так

      .className {white-space:pre;}
      .className:before {content:»Пример: A»;}

      Можно как нибудь без pre?

    19. 4 января 2011

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

      ЦСС — это язык описания отображения разметки, а не инструмент ее модификации (пусть и совсем небольшой). Исходя из этого, те браузеры, что скопировали вывод с «добавленными» кавычками и скобками, сделалм это ошибочно. (О чем и должен быть ошибкоотчет.) Результаты «вставки» через псевдо-классы не должны копироваться вообще. И смысл атрибута «content» именно в этом и кроется — отображение визуальных текстовых вкраплений, которые не оказывают влияния на ДОМ.

      Нетрудно догадаться, когда нам это может пригодиться при решении реальных задач. Допустим, нам необходимо путем ЯваСкрипта собрать текстовое содержимое всех ссылок из ДИВа. При этом для ссылок, ведущих за пределы нашего сайта, верстальщик задал правило:

      div.post a [href^=’http://’]:after, div.post a [href^=’https://’]:after {
      content: ‘ (biohazard)’;
      }

      Таким образом, мы эффективно отмечаем внешние ссылки — изменяем отображение, не меняя при этом содержания. В ДОМе по прежнему реальный текст (который мы вытаскиваем ЯваСкриптом), а на экране монитора — визуальная метка.

      Будь все наоборот, мы получили бы маразм: инструмент для изменения отображения информации меняет саму информацию.

      Таким образом, если задача стоит вывести пользователю [данные], то невозможно использовать ЦСС как инструмент дополнительной их генерации. А вот эти данные подкрасить — пожалуйста.

      В ситуации, когда мы хотим сократить передачу повторяющихся данных, уместно использовать ЯваСкрипт- или ИксСЛ-шаблоны.