Заметки веб-работника
21 октября 2006

«Полосатые таблицы» средствами CSS

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

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

Ланцетники 30 видов Хордовый скелет
Хрящевые рыбы 600 видов Хрящевый скелет
Костные рыбы 20 000 видов Костный скелет
Земноводные 3 000 видов Ажурный скелет
Пресмыкающиеся 6 000 видов Сплошной скелет
Птицы 8 000 видов Сплошной скелет
Млекопитающие 4 000 видов Сплошной скелет

Я поставил себе следующую задачу: не изменяя HTML-файл:

<html>
<head>
<link href="styles.css" type="text/css" rel="stylesheet">
</head>
<body>
<table class="stripy">
   <tr>
      <td>Ланцетники</td>
      <td>30 видов</td>
      <td>Хордовый скелет</td>
   </tr>
   <tr>
      <td>Хрящевые рыбы</td>
      <td>600 видов</td>
      <td>Хрящевый скелет</td>
   </tr>
   <tr>
      <td>Костные рыбы </td>
      <td>20 000 видов</td>
      <td>Костный скелет</td>
   </tr>
   <tr>
      <td>Земноводные</td>
      <td>3 000 видов</td>
      <td>Ажурный скелет</td>
   </tr>
   <tr>
      <td>Пресмыкающиеся</td>
      <td>6 000 видов</td>
      <td>Сплошной скелет</td>
   </tr>
   <tr>
      <td>Птицы</td>
      <td>8 000 видов</td>
      <td>Сплошной скелет</td>
   </tr>
   <tr>
      <td>Млекопитающие</td>
      <td>4 000 видов</td>
      <td>Сплошной скелет</td>
   </tr>
</table>
</body>
</html>
задать отдельные стилевые классы для четных и нечетных строк таблиц с классом stripy.

Стандартный путь

В сети по запросу «полосатые таблицы» или «таблица зебра» ("striped tables", "zebra tables"), легко обнаружить нужные материалы по теме. Как правило, предлагаемые решения – различные вариации одной идеи: используя JavaScript и DOM, находим в HTML-документе требуемые таблицы (класс stripy) и добавляем необходимые классы (к примеру, oddrows и evenrows) их строкам, в зависимости от того, четная это строка или нет. Это может быть реализовано таким образом:

function makeStripy(tabClass) 
{
   var tabs = document.getElementsByTagName("table");
   for (var e = 0; e < tabs.length; e++) 
      if (tabs[e].className == tabClass) 
      {
         var rows = tabs[e].getElementsByTagName("tr");
         for (var i = 0; i < rows.length; i++) // строки нумеруются с 0
            rows[i].className += ((i % 2) == 0 ? " oddrows" : " evenrows");
      }
}

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

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

Идеальное решение

Можно ли решить задачу «чистым» CSS? Можно, если браузер поддерживает CSS3. Изучив спецификации, мы находим там новый псевдо-класс :nth-child(), несомненно появившийся под влиянием сабжа, и благодаря которому необходимые стили задаются очень просто:

/* нечетная строка таблицы класса stripy */
.stripy tr:nth-child(2n+1){
   ...
}

/* четная строка таблицы класса stripy */
.stripy tr:nth-child(2n){
   ...
}

Увы, современные браузеры в абсолютном большинстве пока не поддерживают это. А что же CSS2? Здесь, к сожалению, подходящих инструментов совсем немного. Частично решить проблему можно с помощью селектора + :

.stripy tr { ... } /* 1-я строка */
.stripy tr + tr { ... } /* 2-я строка */
.stripy tr + tr + tr { ... } /* 3-я строка */
.stripy tr + tr + tr + tr { ... } /* 4-я строка */
.stripy tr + tr + tr + tr + tr { ... } /* 5-я строка */

однако данный метод подходит только для таблиц с относительно небольшим и заранее известным числом строк, кроме того, его никак нельзя назвать удобным. Что же остается? Остается использовать дополнительные возможности каждого отдельного браузера. Я попытался найти решение для Windows версий Internet Explorer, Mozilla и Opera.

Internet Explorer

С этим браузером трудностей не возникло. Воспользовавшись способностью IE задавать значения CSS-свойствам динамически с помощью expression(), задачу чередующихся разноцветных строк, к примеру, легко решить следующим способом:

.stripy tr{
   background: expression(this.rowIndex % 2 == 0 ? "#ffe" : "#efe");
}

Для определения этим методом не одного свойства, а целого стиля для соответствующих табличных строк (добавить классы oddrows и evenrows), код придется усложнить, или воспользоваться технологией DHTML Behaviors:

.stripy tr{
   behavior: url(stripy.htc);
}

/* нечетная строка */
.stripy .oddrows{
   background:#ffe;
   ...
}

/* четная строка */
.stripy .evenrows{
   background:#efe;
   ...
}

В приведенном примере реализовывается задуманное: добавляются необходимые классы строкам, в зависимости от того, четная это строка или нет. Такое поведение тега tr описано в файле stripy.htc:

stripy.htc
<?xml version="1.0" ?>
<public:component xmlns:public="urn:HTMLComponent">
   <script type="text/javascript">
   //<![CDATA[
      className += (rowIndex % 2 == 0) ? " oddrows" : " evenrows";
   //]]>
   </script>
</public:component>

Использованные технологии были впервые представлены в Internet Explorer версии 5, соответственно таблицы с классом stripy станут «зебрами», начиная с этой версии.

Gecko

Расширение, подобное DHTML Behaviors воплощено и в браузерах с движком Gecko. Веб-разработчикам позволено, как определять новые элементы на странице, так и переопределять внешний вид, содержимое и поведение стандартных. Для этого используется XBL (XML Binding Language). Данная технология обладает бóльшими возможностями по сравнению с разработкой от Microsoft и, казалось, что с решением поставленной задачи трудностей не возникнет, но не тут-то было.

В CSS XBL-привязка задается подобно behavior с помощью свойства -moz-binding:

.stripy tr{
   -moz-binding: url(stripy.xml);
}

Оставалось в XBL-файле stripy.xml описать поведение нужных тегов, однако, несмотря на все свои усилия, мне не удалось «привязать» ни тег tr, ни другие табличные теги. Причиной этой неприятности оказался Bugzilla Bug 83830: Binding TD cells (with XBL) doesn't work, at all. Как бы это не было обидно, но по всему выходило, что на XBL не стоит рассчитывать. К счастью, решение все же было найдено!

Помощь пришла от известного веб-гуру Дина Эдвардса (Dean Edwards), создавшего XBL-обертку, позволяющую Gecko-браузерам использовать технологию Microsoft DHTML Behaviors, а это значит, для решения задачи можно задействовать тот же файл stripy.htc, что и для IE!

Для этого нам понадобится два XBL-файла: moz-behaviors.xml (непосредственно обертка, взять можно на сайте Дина Эдвардса, весит меньше 7K) и bindings.xml (вспомогательный, промежуточный файл «между» CSS и moz-behaviors.xml, код ниже по тексту). Теперь, чтобы .htc-файл заработал и в Mozilla, необходимо следующее:

  • во-первых, он должен быть валидным ХML-документом (как в приведенном выше примере), так как для его загрузки используется объект XMLHttpRequest;
  • во-вторых, ему следует находиться в одной директории с moz-behaviors.xml и bindings.xml;
  • и, в-третьих, XBL-привязка должна быть оформлена подобным образом:

.stripy tr{
   -moz-binding: url(bindings.xml#stripy.htc);
}

В общем случае все отлично работает, но изменить поведение тега tr все равно не удастся из-за упомянутого выше бага, но умнице Эдвардсу удалось решить и эту проблему. Для нашего случая СSS-код следует дополнить:

/* необходимое дополнение для успешной привязки табличных тегов */
.stripy{
   -moz-binding: url(bindings.xml#table);
}

/* HTC behavior при помощи XBL-привязки */
.stripy tr{
   -moz-binding: url(bindings.xml#stripy.htc);
}

/* нечетная строка */
.stripy .oddrows{
   background:#ffe;
   ...
}

/* четная строка */
.stripy .evenrows{
   background:#efe;
   ...
}

Содержимое файла bindings.xml должно быть следующим:

bindings.xml
<?xml version="1.0" ?>
<bindings xmlns="http://www.mozilla.org/xbl">
   <!-- путь к moz-behaviors.xml по умолчанию (необходимо приписывать #behavior) -->
   <binding id="behavior" extends="moz-behaviors.xml#behavior"/>
   <!-- для табличной привязки -->
   <binding id="table" extends="moz-behaviors.xml#table"/>
   <!-- ссылка на .htc-файл -->
   <binding id="stripy.htc" extends="#behavior"/>
</bindings>

Теперь все отлично работает. Таблицы с классом stripy станут «зебрами» в браузерах на движке Gecko, начиная с версии 1.0.

Opera

К сожалению, этот представитель семейства веб-проводников не располагает механизмами расширения сравнимыми с конкурентами. Да, в 9-й версии появились виджеты, но с их помощью невозможно как-либо повлиять на работу браузера. Что делать? Opera не оставила выбора, кроме как воспользоваться запрещенным приемом. Начиная с 7-й версии, браузер (как впрочем, и IE, начиная с версии 5) обладал интересной «особенностью» – возможностью выполнять скрипты прямо из CSS. Для этого используется любое CSS-свойство, позволяющее в качестве значения указывать путь к файлу. Вот пример такой техники:

body{
   background-image: url("javascript: alert('Hi');");
}

Opera запускает сценарий только для существующих HTML-элементов и только один раз. Почему бы этим не воспользоваться и не позаимствовать скрипт, упоминавшийся выше? Я так и сделал, хотя стоит признаться, с данным приемом возникали определенные сложности и не все сценарии были «послушными»:

function makeStripy(tabClass) 
{
   var tabs = document.getElementsByTagName("table");
   for (var e = 0; e < tabs.length; e++) 
      if (tabs[e].className == tabClass) 
      {
         var rows = tabs[e].getElementsByTagName("tr");
         for (var i = 0; i < rows.length; i++)
            rows[i].className += ((i % 2) == 0 ? " oddrows" : " evenrows");
      }
}
if (window.opera) makeStripy("stripy");

Этот скрипт успешно выполниться в Opera и через CSS:

.stripy tr{
   background-image: url("javascript:function makeStripy(tabClass){var tabs=document.getElementsByTagName('table');for (var e=0;e<tabs.length;e++) if (tabs[e].className==tabClass){var rows=tabs[e].getElementsByTagName('tr');for (var i=0;i<rows.length;i++) rows[i].className+=((i % 2)==0?' oddrows':' evenrows');}} if(window.opera) makeStripy('stripy');");
}

В приведенном примере необходимая функция вызывается напрямую (благодаря проверке if (window.opera) ... это происходит только в Opera), а можно привязать этот вызов к событию onload страницы, мне удалось добиться этого следующим образом:

.stripy tr{
   background-image: url("javascript:(function(){function makeStripy(tabClass){var tabs=document.getElementsByTagName('table');for (var e=0;e<tabs.length;e++) if (tabs[e].className==tabClass){var rows=tabs[e].getElementsByTagName('tr');for (var i=0;i<rows.length;i++) rows[i].className+=((i % 2)==0?' oddrows':' evenrows');}}if(window.opera)window.onload=function(){makeStripy('stripy')};})()");

Оба варианта будут работать в версиях Opera, поддерживающих свойство className объекта HTMLElement для элемента tr, т.е. начиная с версии 7.5. Таблицы с классом stripy успешно становились «полосатыми» и в 9-й версии браузера, однако уже в сборке 8500 Opera утрачивает свою «уникальную способность» – указанный прием больше не работает. Так что, казалось, уже закрытый вопрос, для Opera остается пока открытым.

Итог

Итак, добиться всего задуманного не удалось, но, тем не менее, мы имеем хоть и не полное, но вполне удовлетворительное решение. HTML остается нетронутым, а стилевой файл style.css может выглядеть так:

styles.css
.stripy{
   -moz-binding: url(bindings.xml#table);
}
.stripy tr{
   behavior: url(stripy.htc);
   -moz-binding: url(bindings.xml#stripy.htc);
   background-image: url("javascript:function makeStripy(tabClass){var tabs=document.getElementsByTagName('table');for (var e=0;e<tabs.length;e++) if (tabs[e].className==tabClass){var rows=tabs[e].getElementsByTagName('tr');for (var i=0;i<rows.length;i++) rows[i].className+=((i % 2)==0?' oddrows':' evenrows');}} if(window.opera) makeStripy('stripy');");
}

.stripy tr:nth-child(2n+1){
   background:#ffe;
}

.stripy tr:nth-child(2n){
   background:#efe;
}

.stripy .oddrows{
   background:#ffe;
}

.stripy .evenrows{
   background:#efe;
}

Нам также необходимо добавить три файла (stripy.htc, moz-behaviors.xml и bindings.xml). Как это все работает можно увидеть здесь.


Теги: CSS JavaScript XBL

Комментарии

1
lamka (Пт, 6 Фев 2009 2:10:20 +0200)
 

Ух! Отличная статья. Спасибо!

2
Жора (Пт, 26 Июн 2009 11:50:53 +0300)
 

зачем все эти ужасные хаки?
CSS-то как таковой остался не при чём)

кстати, кто мешает в ff повесить binding на таблицу и подписаться на DOMSubtreeModified?

3
joomla15.ru (Пн, 31 Авг 2009 4:33:14 +0300)
 

А сейчас уже можно работать с этим.. Не работоспособно только в IE - даже в IE8, хотелось бы найти вариант обхода на JS

4
Mr.Carrot (Сб, 19 Сен 2009 9:33:49 +0300)
 

Это можно сделать проще на jquery

5
Денис Шутько (Ср, 26 Май 2010 19:10:59 +0300)
 

ВАЖНО!!! Уточнение для IE:

.stripy tr td{
  background: expression(this.parentNode.rowIndex % 2 == 0 ? "#ffe" : "#efe");
}

6
Георгий (Вт, 8 Фев 2011 12:03:24 +0200)
 

статья отличная, а разметка сайта в десятом хроме плавает туда-сюда, особенно если увеличивать и уменьшать масштаб (длинная горизонтальная прокрутка на мониторе 1440)

7
Konstantin (Чт, 24 Мар 2011 15:47:09 +0200)
 

Спасибо за статью!

_______

Вы можете оставить свой отзыв по данной статье:

  Имя
  E-mail (не публикуется)
  Веб-сайт

Комментарий (Теги форматирования не поддерживаются)