Каскадные Таблицы Стилей

индекс
324,89

Препроцессинг CSS на клиенте

Представьте, что вы пишете блогохостинг и хотите позволить авторам блогов менять свой дизайн. Картиночки там вставлять, цвета менять, пропорции регулировать… Представили? Если хорошо представили, то уже поняли, что без констант и формул в CSS тут не обойтись.

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

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

Напишем XHTML-ку и подключим к ней скин:
<!DOCTYPE html>
<?xml-stylesheet type="text/xsl" href="skin.xsl?user:tenshi/rev:123"?>
<html>
    <head>
        <title>Демонстрация препроцессинга CSS с помощью XSL</title>
    </head>
    <body>
        <h1>Заголовок раздела</h1>
        <h2>Дополнительный текст описывающий этот раздел</h2>
        <div src="index2.xml" srctype="text/xml">
            <a href="index2.xml">Ссылка на подключаемый файл</a>
        </div>
    </body>
</html>

Напишем скин и подключим к нему стили:
<t:stylesheet version="1.0" xmlns:t="http://www.w3.org/1999/XSL/Transform">

    <t:variable name="color.main" select=" '#eee' " />
    <t:variable name="color.add" select=" '#369' " />

    <t:variable name="size.border" select=" 16 " />
    <t:variable name="size.decor" select=" 4 " />
    <t:variable name="size.font" select=" 16 " />

    <t:include href="styles.xsl?rev:123" />

</t:stylesheet>

И последний штрих — вычислим дополнительные константы, подставим их в стили и специальным шаблоном впрыснем их в результирующий XHTML:
<t:stylesheet id="t:stylesheet" version="1.0" xmlns:t="http://www.w3.org/1999/XSL/Transform">

    <t:output method="html" doctype-public="-//W3C//DTD XHTML 2.0//EN"/>

    <t:variable name="size.padding" select=" $size.border * 2 " />
    <t:variable name="size.font.header" select=" $size.font * 2 " />

    <t:variable name="mixin.header">
        margin: 0;
        text-align: center;
        text-overflow: ellipsis;
        overflow: hidden;
        line-height: 1em;
    </t:variable>

    <t:template match=" head " mode="content">
        <t:apply-templates select=" * " />
        <style>/*<link type="text/css" rel="stylesheet" href="data:text/css,*/{{}}

html {{
    background: {$color.main};
    margin: 0;
    padding: {$size.padding}pt;
}}

h1 {{
    {$mixin.header}
    border: {$size.border}pt solid {$color.add};
    padding: {$size.padding}pt;
    font-size: {$size.font.header}pt;
    color: {$color.add};
}}

h2 {{
    {$mixin.header}
    font-size: {$size.font}pt;
    font-style: italic;
    color: {$color.main};
    background: {$color.add};
    padding: 0 {$size.border}pt {$size.border+$size.decor}pt;
}}

p {{
    border-top: {$size.decor}pt dashed {$color.main};
    font-size: {$size.font}pt;
    color: {$color.main};
    background: {$color.add};
    margin: -{$size.decor}pt 0 0;
    padding: {$size.padding}pt;
    text-overflow: ellipsis;
    overflow: hidden;
}}

        /*"
/>*/</style>
        <script>
            if( /webkit/i.test( navigator.userAgent ) ) new function(){
                var styles= document.getElementsByTagName( 'style' )
                var style= styles[ styles.length - 1 ]
                style.innerText= unescape( style.innerText )
            }
        </script>
    </t:template>

    <t:template match=" @* | node() ">
        <t:copy>
            <t:apply-templates select=" @* " />
            <t:apply-templates select=" . " mode="content" />
        </t:copy>
    </t:template>
    <t:template match=" processing-instruction() " />

    <t:template match=" node() " mode="content">
        <t:apply-templates select=' node() ' />
    </t:template>
    <t:template match=" *[ @src and contains( @srctype, 'xml' ) ] " mode="content">
        <t:apply-templates select=' document( @src )//body/node() ' />
    </t:template>

</t:stylesheet>

Как видно мы по прежнему можем использовать возможности XHTML2 для вставки сторонних документов, а также применять довольно гламурный синтаксис для вставки в CSS констант, формул и примесей.

Совместимость: все попурярные настольные браузеры и часть продвинутых мобильных.
+31
9 мая 2010, 02:37
77

комментарии (57)

+3
mrskam #
Только в сафари 4.0.5 (win7) не захотело работать, в отличии от вашего примера из предыдущей статьи.
А так все норм, спасибо за хорошие рецепты.
+3
tenshi #
поправил
+2
catsmile #
Каждый раз, когда я думаю, что отображение HTML есть вещь устоявшаяся, открываются новые стороны. Спасибо.
+7
Wott #
А чем отличается от дополнительного стиля, который перекрывает дефолтные, кроме того что грузит клиента?
0
tenshi #
проще в поддержке
+2
Wott #
В каком смысле?

Вот есть параметры пользователя, я их вытащил из базы и засунул в шаблон. Мне лично пофиг какого формата этот шаблон.
При изменении параметра я все равно буду править шаблон и базу и страницы с установками. И объем не меняется.
0
tenshi #
а я не буду
+1
Wott #
Ну тогда в нем параметры останутся прежними.
0
tenshi #
с чего бы?
+1
Wott #
Потому что не измененные
0
tenshi #
что они забыли в других местах кроме файла скина??
+1
Wott #
lol
например в форме для изменения
0
tenshi #
в исходниках формы? что в вёрстке забыли конкретные значения полей формы?
+1
Wott #
я разве сказал в исходниках? в значении полей формы.
0
tenshi #
они возьмутся из скина
+1
Wott #
ок, хотя мои есть в том сомнения что это эффективно.
Как они из формы попадут в новый скин?
0
tenshi #
специально обученные обезьянки будут принимать пост запросы и впечатывать их в соответствующие файлы х)
+9
pepelsbey #
Тем, что это любимая технология автора поста ^_^
+1
tenshi #
не спроста
+2
sitehound #
А внешний css можно так же обработать?
+2
tenshi #
нет. только css трансформированный в xsl
+2
heruvim #
круто, возможно пригодится. спасибо автору, очень интересны статьи на эту тематику, раньше никто про это не писал.
+7
SwampRunner #
не круто
+3
orloffkirill #
Читаю вас уже не первый раз, и опять не первый раз убеждаюсь в том, что можно и не использовать XSLT. Пост в очередной раз задел за живое, поэтому продолжаю попытки избежать использования XSLT с помощью PHP.

Создаем для красоты три файла и пользуемся стандартным набором PHP:
1. css.php
<html><head><title>TEST CSS</title>
<style type="text/css">

<?php
  require_once 'css2.php';
  $file = file_get_contents('css2.css');
  echo str_ireplace(array_keys($style_array), array_values($style_array), $file);
?>

</style>
</head>
<body>
<p class="body__p">TEST</p>
</body>
</html>


2. css2.php
<?php 
$style_array = array(

	'$BODY__P-font-size' => '68px',
	'$BODY__P-color' => '#FF6600'

);
?>

3. css2.css
.body__p{
	font-size: $BODY__P-font-size;
	color: $BODY__P-color;
}


из кода все понятно, но все же кратенько:
css.php — основной шаблон, в котором подключается все остальное,
css2.php — перечень переменных,
css2.css — собственно файл css.

Насчет скорости работы сказать не могу — возможно, есть более быстрые решения с RegExp, или использование str_replace или в написании собственного парсера. Но при увеличении размера XML-файла время обработки тоже увеличится. Конечно, стоит добавить несколько проверок, например, на наличие файла. Этот так-называемый-парсер был создан за пару минут, но показывает, что все возможно банальным PHP еще даже 4ой версии, с которой я чертовски давно начинал. Ну, и конечно, все это можно подключить в еще один файл или использовать этот для создания конструкции вида
<link rel="stylesheet" href="/css.php" type="text/css" />.


Если есть желание, все это можно переписать под объекты и использовать что-то типа
$body->p->maintext->color
или, к примеру
$body->p->font['size'].
+2
SwampRunner #
согласен, всё должно быть просто и красиво.
+1
Grundiss #
Программные интерфейсы должны быть простыми и красивыми. Здесь они таковыми являются.
Что до внутреннего устройства программы: XSLT-верстарь заценит красоту реализации.
Хотя вообще, я свое мнениениже изложил: habrahabr.ru/blogs/css/93118/#comment_2823530
0
tenshi #
абзац про кэширование ты проигнорировал по какой причине?
+1
orloffkirill #
А где он? Пардон, но на странице я обнаружил два слова, содержащие фразу «кэш» («cache» — вообще отсутствует) — в Похожих публикациях и в твоем посте, но никак не в топике.

Но если насчет кэширования — то тут все просто. Есть туча методов кэширования, например:
— Правильно настроенный апач (mod_expires + mod_headers + ETAG) — css (и не только) будут выходить с ответом «304 Not Modified». Но это, имхо, необходимо реализовывать на большинстве серверов. А чтобы css был новым, всегда когда его обновляем, можно прописать что-то типа: css2.css?20100510.
— Опять же в php можно очень просто подключить memcache и закэшировать все это. Плюс, если уж изврат, то хранить в базе, там кэш тоже есть.
0
tenshi #
второй абзац.

правильно реализованное кэширование — не делает запроса к серверу вообще.
+1
orloffkirill #
Ну как это не делает? Сервер на каждый запрос должен выдать какой-либо ответ — 304 или 200 или даже 302.

А в остальном — в чем проблема-то?

Дефолтный css грузится сначала с site/skin/default/css.css и дает ответ 304, затем грузится другой css с site/skin/newskin/css.css и, где надо, оверайтит дефолтный. Если браузер уже грузил этот файл, ему будет ответ 304.

Как иначе-то? Или я не понимаю вопроса. В топике говорится про неизбежные дополнительные вычисления на сервере, и, в следствие этого, мы вызываем xsl-процессор для вычисления разницы. А только на php мы вызываем php-интерпретатор для получения файла css и Memcache нас спасает.
+1
tenshi #
есть правила, которые не зависят от скина. и при посещении блогов 10 разных людей мы получим 10 кратную загрузку этих правил

«где надо» — тяжело поддерживать
+1
orloffkirill #
Чем же вариант на XSL спасает нас от этого?

Может, я неверно выразился ранее, ибо /default/css.css — это и есть те самые правила, не зависящие от скина. А /newskin/css.css — это скин одного человека. A /newskin2/css.css — это скин другого человека. Можно еще иметь некий — /firstskin/css.css — первоначальный скин, который будет для всех один.

«где надо» — так же тяжело поддерживать, как и в примере в топике, или так сложен сам процесс оверайта?
+1
tenshi #
сложен процесс синхронизации изменений. когда изменив селектор в дефолтном стиле тебе придётся изменить этот же селектор и во всех скинах.
+1
orloffkirill #
Можно пример?
+1
tenshi #
def: p, dl { border: 1px solid steelblue }
skin: p, dl { border-color: red }

надо добавить аналогичный бордер и для blockquote
+1
orloffkirill #
В данном случае согласен!

В PHP либо надстройка над css-файлом, которая прописывает все стили, которые будут добавлены. Но опытный программист (как и опытный верстальщик) должен учитывать практически все варианты. Это как с проектированием БД — если после долгого использования ты изменяешь БД — это неправильно (опять же, есть исключения).

Если бы необходимо было дописать еще один селектор в уже рабочий проект, я написал бы генератор или парсер css-файлов юзеров на PHP. А с новым можно подольше позаморачиваться.
+1
tenshi #
эм… и это получилось бы проще, чем предложенный мною способ? х)
+1
orloffkirill #
Парсер, скорее всего, конечно, не проще.

Но, поскрипев мозгами (чего стоит делать, несомненно, чаще), предлагаю такой способ:

Дописываем в файл /newskin/css.css следующее:
$admin_custom_zone
$STOR->user_custom_zone
.BODY__P{/*..style..*/}

В файл, где хранятся все дефолтные значения скина css2.php
'$admin_custom_zone' => 'blockquote{border: 1px solid steelblue;}'
Это значение $admin_custom_zone, конечно, тоже можно хранить в $STOR (БД или файл) для удобного доступа из админки.

Собственно, дополнительную часть скина ($STOR->user_custom_zone) храним в БД или в файле, не суть.

Теперь при добавлении blockquote, он добавится во все css, а юзеру позже придется в своей «user_custom_zone» добавить
blockquote{border-color: red;}
, он сохраняется в $STOR, а при следующей загрузке обновляется.

Твой способ, конечно, где-то проще, но он находится на границе с очень редковстречаемой частностью. Но городить для этой частности XSL и все связанное — по-моему, как уже неоднократно употреблялось здесь, изврат.

Вообще, изначально, предложенный мною вариант был создан с целью показать, что все можно сделать без XSL довольно быстро и просто. Теперь, если полезли в частности, как и ожидалось, вариант усложнился, скрип мозгов стал громче. Двоеточие-скобка
+1
tenshi #
ты предлагаешь дать юзеру возможность писать напрямую css правила?
+1
orloffkirill #
Определить список разрешенных селекторов в таблице «Кастом_нью_селектор»: «id | selector_name | default_value».
Создать таблицу связей: «selector_id | user_id | value».
Кстати, вместо user_id может быть, стоит использовать site_id.
И вот теперь уже с помощью одного foreach можно не давать юзеру писать все цсс-правила, а только значения.

Но, в принципе, а чем я хуже людей, сделавших Social Engine? В этом движке каждый пользователь может писать css-правила. Не блогохостинг, но соцсеть.

А вообще — да, предлагаю, но только в платном аккаунте.
+1
tenshi #
наверно тем, что не видишь к какому гигантскому геморрою приводит твоя боязнь использовать xslt.
+1
orloffkirill #
Не вижу. Я вижу другое — нет смысла использовать что-то немерянно-громоздкое там, где можно прекрасно без этого обойтись. Данная ситуация — это большая частность, в которой xslt, возможно, немного выигрывает в каком-то моменте.

Но это только в этом случае. А теперь надо представить, что xslt будет обслуживать не только этот момент, но и все остальное, иначе как это — мы используем всю мощь xslt только тут, нафига тогда оно нам надо?
+1
tenshi #
пхп ты тоже на всю мощь используешь? у тебя все скрипты многопоточные? ведь там есть такая возможность.
+1
orloffkirill #
Я к тому, что нам придется загружать XSLT-процессор постоянно или только для выполнения такого маневра.
+2
tenshi #
он и так постоянно загружен
+1
orloffkirill #
у меня — нет
0
tenshi #
ты пропатчил свой фаерфокс?
+1
orloffkirill #
Эммм… наверное, не до такой степени
+9
Grundiss #
По-моему изврат. Элегантно, но — изврат. Не надо использовать XSLT там, где ему не место.
JS, говорите, отключают? Кто? Либо те, кто в этом понимает (а, значит, и понимает, что интерактивности от странички можно не ждать), либо он отключен на рабочем компе админом (ибо нефиг в рабочее время в бложики писать).
0
tenshi #
причём тут интерактивность и бложики?
0
egorinsk #
Мда, у вас конечно очень интересные идеи в постах, но почему всюду надо использовать этот богомерзкий нечитабельный XML??
+10
Grundiss #
did xml kill your family?
0
orloffkirill #
Пардон за оффтопик, но напомнило
«Silence, I kill you!»
+2
AlDev #
альтернатива, я так понимаю, json со своими закрытиями объектов и массивов аля ]]}]]}}]}}}?
+1
tenshi #
я специально добавляю слили через аттрибут и использую 2 хака — один для ИЕ, другой — для вебкита. именно для того, чтобы в цсс небыло xml-я
НЛО прилетело и опубликовало эту надпись здесь
+1
tenshi #
оборачивание в style, ибо он не поддерживает data-uri

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