CSS-live.ru

Абсолютное позиционирование в ячейках таблицы, в Firefox

Многие верстальщики наверняка сталкивались с неприятной особенностью, которая касается абсолютного позиционирования в ячейках таблицы, в Firefox. Неприятность заключается в том, что в браузерах Firefox, вплоть до последней версии (Firefox-9 на данный момент) нет поддержки position : absolute как в таблице, так и в самих её ячейках. Т.е. мы, например, не сможем позиционировать какой нибудь блок или элемент, относительно <table> или <td>, ну или любых других элементов таблицы, типа <tr>, <tbody> и т.д.

По моим наблюдениям, на форумах, часто задают вопрос с просьбой помочь в решении этой проблемы. Да, и мне порой, эта неприятность доставляет массу неудобств, подталкивая к изобретению обходных путей и прочих костылей . В этой статье, я предлагаю всё же выяснить, что это на самом деле, баг или фича, и можно ли считать Firefox неправым в данной ситуации? А так же найти наконец решение этой проблемы.

Для начала предлагаю посмотреть одну из ситуаций, где мы сможем увидеть, насколько полезным может оказаться работающий position : absolute в ячейках

Пример — Описание товаров

Представим себе ситуацию. Интернет магазин, таблица описания товаров, в ячейках картинка-товар, его цена и описание.
Задача: Сделать разделители между ячейками, но, только необычные, ну скажем, чтобы они были разноцветными и растягивались от 10px сверху ячейки и до 10px не доходя низа. Вот как это выглядит примерно.

Оранжевые полоски сверху означают конец строки таблицы и начало новой. В остальном, как и планировалось, обтекаемое изображение, и текст справа. А вот как раз градиентные разделители слева от ячеек — это и есть то, о чем мы говорили, т.е. отдельные позиционированные блоки, которые берут своё начало сверху с отступом 10px и заканчиваются почти внизу, не доходя до низа так же 10px. Для примера я сделал псевдоэлемент у ячеек, он и будет являться нашим разделителем.

table td {
	vertical-align: top;
	padding: 10px;
	padding-left: 30px;
	position: relative;
}
	td:after {
		background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
		background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%, rgba(0,0,0,1) 100%);
		position: absolute;
		width: 20px;
		bottom: 10px;
		top: 10px;
		left: 0px;
		content: '';
		border-radius : 5px;
	}

Остальной код, я думаю, показывать смысла нет, там все просто и понятно.

В общем всё вроде бы логично, самим ячейкам у нас прописан position: relative, а для псевдоэлемента position: absolute, и последний при этом прекрасно чувствует первого, отталкиваясь своими начальными координатами именно от него. Но, так считают к сожалению не все… не все браузеры. У Firefox на этот случай свой взгляд.

По рисунку можно понять, что в Firefox "абсолютные" блоки превратились в один большой блок, родителем которого уже является окно браузера, а не ячейка таблицы. Это досадное явление ясно даёт понять, что нам придётся либо отказываться от position: absolute вообще, либо искать решение данной проблемы.

Этот пример не единственный. Задач с применением position: absolute в таблице множество и все они рушатся в один момент, когда на поле появляется Firefox. Взять хотя бы обычный пример с одним, простым элементом, который нужно выровнять относительно ячейки таблицы. Сделать, скажем, углы для ячеек или же просто какие-то другие вещи, касающиеся абсолютного позиционирования в таблице. И все эти попытки сразу же становятся тщетными, когда на поле появляется Firefox.
Но, не стоит расстраиваться раньше времени, мы же тоже не лыком шиты, не правда ли?

Баг или фича?

Перед тем, как переходить к решению данной проблемы, давайте разберёмся, что же на самом деле происходит и почему Firefox ведёт себя не так, как остальные браузеры.
За ответом я отправился в спецификацию и обнаружил там несколько интересных выдержек, касающихся нашей ситуации. На самом деле ситуация немного двоякая, и сейчас вы поймёте, почему.
В пунктах 9.3 про position : absolute и в пункте 10.1, части четвёртой говорится следующее.

absolute
The box's position (and possibly size) is specified with the 'top', 'right', 'bottom', and 'left' properties. These properties specify offsets with respect to the box's containing block.

Что переводится примерно, как:
Элемент с position: absolute, которому заданы какие либо из свойств top, right, bottom, или left имеют смещение относительно содержащего их контейнера.
И, как можно заметить, нигде не указано про какие либо исключения. Т.е. ячейка таблицы — это такой же контейнер для своих потомков, как и другие элементы.

Или вот, например

If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed'

Если элементу указан position: absolute, то он позиционируется относительно ближайшего предка с position: absolute, relative или fixed.
Что снова говорит о том, что ячейка таблицы — не является никаким исключением из правил, и так же вполне может быть ближайшим предком, как и другие элементы.

Но, с другой стороны, если снова обратиться к пункту 9.3 по части relative, то там мы уже можем увидеть следующее…

The box's position is calculated according to the normal flow (this is called the position in normal flow). Then the box is offset relative to its normal position. When a box B is relatively positioned, the position of the following box is calculated as though B were not offset. The effect of 'position:relative' on table-row-group, table-header-group, table-footer-group, table-row, table-column-group, table-column, table-cell, and table-caption elements is undefined.

Смысл написанного в том, что, как оказалось, спецификация чётко не определяет, как должен вести себя position:relative на таблице и её элементах. Ключевая фраза здесь:

The effect of 'position:relative' on table-row-group, table-header-group, table-footer-group, table-row, table-column-group, table-column, table-cell, and table-caption elements is undefined.

Другими словами, эффект от применения position:relative на табличных элементах не определён (undefined). А значит нет чёткого правила или указания, как он должен себя вести относительно таблицы и её табличных потомков. В связи с чем, в этом случае, браузерам предоставлена полная свобода действий и трактовки своих правил. Браузеры сами вольны выбирать то, как будет вести себя position:relative в ячейках таблицы и absolute по отношению к ним. К сожалению трактовка своих правил оборачивается для нас не очень радостно.

Выходит, что обвинять Firefox не в чем, ведь, по сути, он придерживается правил спецификации, не нарушая их. Другое дело, что нам — разработчикам, от этого не легче. Получается, что все браузеры решили этот вопрос в нашу пользу, но вот Firefox — нет. Интересно, что же его могло удержать?
Скорее всего, разработчики Firefox считают, что сами ячейки не являются объектом, который может вести себя как вырванный из потока элемент. Таблица сложный и одновременно простой элемент, там и без relative хватает проблем.
С одной стороны их конечно же можно понять, но с другой, почему бы не сделать такую возможность и не пойти разработчикам на уступки? Этот вопрос останется для нас загадкой. Надеюсь лишь, что в будущих версиях Firefox всё же, изменит свою точку зрения в отношении этой проблемы.

Решение

Забегу чуть вперёд и немного огорчу вас, сказав, что на мой взгляд, идеального решения данной проблемы — не существует. Решая эту задачу (не один день), я погрузился в неё по уши, пытаясь найти хоть какую-то зацепку и развить её по максимуму. Firefox оказался очень упрямым, в этом отношении, браузером, и ни в какую не хотел поддаваться на мои уговоры. К тому моменту, я уже прекрасно понимал, что заставить Firefox идеально применять relative к ячейкам таблицы мне не удастся (отчасти мне это всё же удалось), и что, для того, чтобы заставить работать в них absolute (сохраняя при этом чёткое поведение ячеек), мне понадобится, как минимум дополнительный контейнер, который и будет точкой отсчёта для абсолютного позиционирования.
Но, как вы, возможно, уже догадались, что, чтобы, например, прижать блок с position: absolute к низу ячейки таблицы, нам необходимо каким либо образом растянуть контейнер внутри неё на 100% по высоте самой ячейки.

Такого поведения легко добиться, если, например, у ячейки будет выставлена фиксированная высота. Тогда блок-контейнер внутри неё, будет чувствовать эту высоту и подхватит её незамедлительно. Но, что делать, если высота ячейки неограничена и зависит от её содержимого? Вот здесь-то уже и приходит долгожданный тупик, выйти из которого, становится практически невозможно. Ведь контейнер должен откуда-то брать высоту, точнее у своего родительского элемента, а если она неизвестна, то и его высота будет автоматической, т.е. подстраиваться под содержимое.
В общем, в итоге, передо мной встала задача, каким либо способом заставить контейнер чувствовать высоту ячейки, при том, что высота последней — автоматическая и должна подстраиваться под высоту своего контента. Т.е. мне предстояло сделать практически невозможное, сотворить что нибудь эдакое, применяя только лишь CSS.

Повозившись приличное время с этой задачей, я понимал, что, видимо без JS здесь и правда не обойтись. Но одним прекрасным днём "чудо" всё таки произошло и я случайно наткнулся на одно интересное решение. И какого было моё удивление, когда я увидел, насколько простым оно оказалось.

table { border-collapse: collapse; width: 100%;}

	/* Удивительное решение */
	table tr { height: 1px;}

		table td {
			vertical-align: top;
			position: relative;
			/* Растягиваем ячейку на 100% по высоте родителя */
			height: 100%;
		}
			td:after {
					background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
					background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%, rgba(0,0,0,1) 100%);
					position: absolute;
					width: 20px;
					bottom: 30px;
					top: 30px;
					left: 0px;
					content: '';
					border-radius : 5px;
				}

				/* Да, да, вы не ошиблись. Это грязный хак для Firefox */
				@-moz-document url-prefix() {

					/* Перевешиваем позиционирование на контейнер. Для Firefox */
					.psyPositionContainer {
						position: relative;
						height: 100%;
						padding: 0 10px;
						padding-left: 30px;
					}
					.psyPositionContainer:after{
						background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
						background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%, rgba(0,0,0,1) 100%);
						position: absolute;
						width: 20px;
						bottom: 30px;
						top: 30px;
						left: 0px;
						content: '';
						border-radius : 5px;
					}
					td:after{ display: none;}
					td:first-child .psyPositionContainer:after { background: none;}
				}

                       /* Дополнительные стили, не играющие роль */
			tr { border-top: 5px solid #F60;}
			td:first-child:after { background: none;}
			.price { font: bold 14px "Trebuchet MS", Arial, Helvetica, sans-serif; color: #E76D13;}
			.content { overflow: hidden;}
			table td img { float: left; margin-right: 10px;}

Сразу приведу скриншот с результатом.

Ну, а теперь, по порядку, что же мы тут натворили. Начнём пожалуй с самой изюминки…

 table tr { height: 1px;}

Вы может быть не поверите, но эта строка кода и является решением данной проблемы! Да, именно проставление фиксированной высоты строке, и даёт такой эффект. Благодаря этому трюку, у нашей строки появляется фиксированная высота, в связи с чем — ячейка внутри неё, сможет её почувствовать. Т.е. сделать фактически то, что мы, по сути, и хотели получить.
Но, как же это работает? Давайте разберёмся…
За ответом на этот вопрос я отправился к моей любимой спецификации, и вот что она смогла мне поведать.

The height of a 'table-row' element's box is calculated once the user agent has all the cells in the row available: it is the maximum of the row's computed 'height', the computed 'height' of each cell in the row, and the minimum height (MIN) required by the cells.

Если сказать нашим языком, то это звучит примерно так:
"Высота элемента типа table-row рассчитывается, когда браузер получил все ячейки строки: она равна максимальному из рассчитанной (по каскаду) высоты строки, рассчитанной высоты каждой ячейки и минимальой высоты, требуемой для контента ячеек"
Т.е. проще говоря, выбирается максимальное из следующего: height самой строки, height наибольшей ячейки и, грубо говоря, offsetHeight самого длинного контента. А результат как бы заносится в height строки.
Ну, а поскольку формально высота tr-шки указана, td с height:100% считает своим долгом подхватить эту высоту и передать дальше своим потомкам. Видимо, так сделано для единообразия учета родительской высоты.

Почему этот алгоритм не работает в других браузерах? Видимо, дело в том, что форматирование таблиц — вообще самая запутанная и противоречивая часть спецификации CSS, по многим пунктам сами стандартисты еще не пришли к общему мнению, как ее трактовать. Но нам вполне достаточно, что Firefox ведет себя так, как нужно.

Далее по списку идёт следующий код.

table td {
	vertical-align: top;
	position: relative;

	/* Растягиваем ячейку на 100% по высоте родителя */
	height: 100%;
}

Здесь мы задаём самой ячейки относительное позиционирование. Это для того, чтобы все браузеры, кроме Firefox, при абсолютном позиционировании внутри ячейки, отталкивались именно от неё. Браузеру Firefox эта запись ничего не скажет, он её просто проигнорирует.
Ну а так же с помощью height: 100% мы растянули ячейку на всю высоту строки, которая в свою очередь, как мы выяснили, растягивается в зависимости от контента. И тут как раз наоборот, для Firefox эта запись даст положительный эффект, в отличии от других браузеров, на которые эта правило никак не повлияет.

td:after {
...
}

Здесь я не стал копировать весь код. Тут всё просто, мы создаём абсолютно позиционированные псевдоэлементы для наших ячеек, видеть которые будут все бразеры, кроме Firefox. Для него чуть позже мы отключим это правило. Там же, вы и узнаете, для чего это нужно.

@-moz-document url-prefix() {

	/* Перевешиваем позиционирование на контейнер. Для Firefox */
	.psyPositionContainer {
		position: relative;
		height: 100%;
		padding: 0 10px;
		padding-left: 30px;
	}
		.psyPositionContainer:after{
			background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
			background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%, rgba(0,0,0,1) 100%);
			position: absolute;
			width: 20px;
			bottom: 30px;
			top: 30px;
			left: 0px;
			content: '';
			border-radius : 5px;
		}
		td:after{ display: none;}
		td:first-child .psyPositionContainer:after { display: none;}

}

А вот тут важный момент. Как вы уже поняли, к сожалению нам пришлось воспользоваться нацеленным хаком для Firefox. Но это скорее вынужденная мера, так как внутри этого хака скрываются правила, которые, во-первых понимает только сам Firefox, а во-вторых вешают стили для контейнера, которых не должны видеть другие браузеры. Первые стили касаются самого контейнера, на который мы вешаем position: relative и height: 100%. Теперь блок будет растянут на всю высоту ячейки и его потомки с абсолютным позиционированием будут плясать от него.
Нам нельзя было допустить, чтобы другие браузеры это увидели, потому что, как мы уже выяснили ранее, height: 100% не даёт в них никакого эффекта, ячейка не будет подхватывать высоту и следовательно, раз нет чёткой высоты, значит и контейнер внутри ячейки так же не будет растянут на 100%. Это чревато тем, что, если, например, в контейнере будет абсолютный блок, который должен будет прижиматься к низу контейнера, а, по сути, ячейки, то этот самый "низ" для него будет отсчитываться от низа самого контейнера, который в свою очередь растягивается лишь на высоту своего контента.

Ну, и дальше по курсу идёт создание псевдоэлемента, но для контейнера, а не для ячейки. А так как, никто кроме Firefox этого не увидит, то и псевдоэлемент для контейнера будет создан именно в нём.

Ну и завершают эти нацеленные стили две записи, первая из которых (td:after{ display: none;}) выключает псевдоэлемент у ячеек таблиц. В Firefox он нам не нужен, так как его "ячейка" — это сам контейнер, ну а вторая запись (td:first-child .psyPositionContainer:after { display: none;}) нужна лишь для выключения псевдоэлемента в первой ячейке.

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

1. Значит мы выяснили, что правило tr { height: 1px;} является, по сути, решением, и помогает нам включить чувствительность высоты строки в нужном нам браузере — Firefox, но полностью игнорируется в других браузерах. А так же это помогает ячейкам подхватывать высоту строки, которая увеличивается и фиксируется в зависимости от самого высокого контента в ячейках этой самой строки. Т.е, по сути, это и есть главное решение.
2. Так же мы поняли, что, в связи с тем, что отличные от Firefox браузеры — не растягивают контейнер на всю высоту родительской ячейки, потому что попросту не знают её, нам потребовалось переложить всё позиционирование на контейнер для Firefox и на ячейки для иных браузеров. Помог нам в этом грязный, но вынужденный хак.

Так что, при помощи небольших манипуляций с кодом, мы, наконец, получили неплохое решение. Да, и не стоит забывать, что в нашем случае, для демонстрации процесса, мы использовали сгенерированный псевдоэлемент :after. На его месте могло бы оказаться множество других, абсолютно позиционированных блоков.
В общем, это решение — было скорее общим, как бы знакомством с самой сутью, главной частью. Но, как уже было сказано выше, я продвинулся чуть дальше и обнаружил ещё немного полезных, и в тоже время, интересных вещей.

Общее решение

Абсолютное позиционирование без контейнера

Как оказалось, всё таки есть одно решение, которое позволило бы нам отказаться от контейнера и применять ячейки таблицы в качестве точки отсчёта для абсолютного позиционирования, в Firefox.

Почему я не применил его в качестве основного? Дело в том, что это решение подходит скорее для частного случая и имеет ряд недочётов. В общем перейдём сразу к коду.

<table><tbody>
	<tr>
		<td class="through-one">
			<img src="../img-td.jpg" />
			<div class="content">
				<div class="price">12000 рублей</div>
				<p>Описание товара, Описание товара, Описание товара</p>
			</div>
		</td>
		<td>
			<img src="../img-td.jpg" />
			<div class="content">
				<div class="price">12000 рублей</div>
				<p>Описание товара, Описание товара, Описание товара</p>
			</div>
		</td>
		<td class="through-one">
			<img src="../img-td.jpg" />
			<div class="content">
				<div class="price">12000 рублей</div>
				<p>Описание товара, Описание товара, Описание товара</p>
			</div>
		</td>

	</tr>
	<tr>
		<td class="through-one">
			<img src="../img-td.jpg" />
			<div class="content">
				<div class="price">12000 рублей</div>
				<p>Описание товара, Описание товара, Описание товара</p>
			</div>
		</td>
		................
	</tr>
</tbody></table>

table { border-collapse: collapse; width: 100%;}

	/* Удивительное решение */
	table tr { height: 1px;}	

		table td {
			position: relative;
			height: 100%;
		}
			.through-one { display: block;}

			td.through-one:after {
					background: #303;

					background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);

					position: absolute;
					width: 20px;
					bottom: 30px;
					top: 20px;
					left: 0px;
					content: '';
					border-radius : 5px;

				}

			/* Дополнительные стили, не играющие роль */
			tr { border-top: 5px solid #F60;}
			.price { font: bold 14px "Trebuchet MS", Arial, Helvetica, sans-serif; color: #E76D13;}
			.content { overflow: hidden;}
			table td img { float: left; margin: 0 10px 0 30px;}

Ну, начнём с того, что, как вы могли видеть, я немного поменял структуру кода, убрав контейнеры и повесив классы (through-one) на некоторые ячейки. Почему на некоторые, вы поймёте чуть позже, а сейчас сама суть. Дело в том, что, оказывается, с помощью display:block ячейкам таблицы можно задавать блочное поведение, что с одной стороны оставит их такой же ячейкой, а с другой — позволит им применить по отношению к себе position: relative, что сделает их точкой отсёта для абсолютно позиционированных потомков, т.е. такими же, как и обычный блочный элемент. И соответственно, в этом случае, браузеру Firefox — уже не понадобятся никакие контейнеры и прочие вспомогательные блоки. Как видно из скриншота, в нём этот приём работает замечательно.

А теперь, как это работает. Суть в том, что при постановке display:block ячейкам (table-cell-ам), которые ещё при этом находятся в строках (table-row), они автоматически оборачиваются анонимным table-cell'ом, и тем самым, снова становятся в ряд со своими соседскими ячейками. Но тут есть один важный момент! К сожалению нельзя вешать display:block на две, (и больше) идущие подряд ячейки, так как в этом случае анонимный table-cell обернёт не каждую ячейку в отдельности, а сразу обе, что приведёт к разрушению всей таблицы. Вот почему я и использовал классы только лишь у каждой второй ячейки. Мне было нужно, чтобы соседние ячейки всегда были родными.

У этого способа есть ещё ряд особенностей, о которых следует знать.
1. Для данного приёма пришлось так же использовать нацеленный хак для Firefox. Иначе, в связи с блочным поведением ячейки, другие браузеры сжали бы её до высоты контента, что привело бы к неправильному позиционированию внутри неё, а точнее блок с position: absolute внутри ячейки не смог бы растянуться на всю её высоту, а только лишь на высоту содержимого. Ведь именно так и работает обычный блочный элемент с автоматической высотой.
2. Вертикальное выравнивание в переделанных ячейках попросту перестаёт работать, как и в обычных элементах блочного уровня.
* Кстати, стоит заметить, что, и в предыдущем, общем примере, в связи с контейнером внутри ячейки, вертикальное выравнивание так же отказывается работать. Чуть позже мы попробуем решить эту проблему.

В общем этот метод вполне подойдёт для какого нибудь частного случая, но, думаю, не больше, так как факт преобразования ячейки "через одну" + отказ от вертикального выравнивания, не даёт нам в полной мере использовать его в наших проектах.
Да, и так же следует иметь ввиду, что вместо display:block вполне подойдёт и display:inline-block или float:left.

Вариант с чередованием ячеек

Вертикальное выравнивание

Вот здесь уже немного сложнее, но начнём по порядку. Изначально было ясно, что с появлением любых видов контейнеров или со сменой поведения ячеек (я имею ввиду всё то, что мы прошли выше), о вертикальном выравнивании через vertical-align стоит позабыть. Это жертва, которую пришлось принести за решение другой, и уже немаловажной проблемы — растягивание ячейки по высоте. Ну хорошо, отдали, но что же теперь делать? Как же при таких раскладах вернуть вертикальное выравнивание?

На этом этапе передо мной встал только один вопрос "Какие у нас ещё есть способы выровнять содержимое по вертикали?" table-cell по понятной причине отпадает. Это возврат к истокам, туда, откуда мы начали. position: absolute… ммм… нет, тоже не подходит, иначе пропадает высота по содержимому. В общем рассуждая на эту тему, я пришёл к выводу, что помочь нам в решении этой задачи может только одна вещь, это эмуляция строки с помощью display: inline-block. Ведь, как известно, vertical-align:middle хорошо работает с инлайновыми элементами, так что стоит попробовать, поэтому пойдём по нарастающей.

Эмуляция строк с одним контейнером

Первый на очереди вариант с чередованием и преобразованием ячеек.

<table>
	<tbody>
		<tr>
			<td>
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
			<td class="through-one">
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
			<td>
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
		</tr>
		<tr>
			<td class="through-one">
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
			<td>
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
			<td class="through-one">
				<div class="wrapper"><img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</td>
		</tr>
	</tbody>
</table>
table { border-collapse: collapse; width: 100%;}

	/* Удивительное решение */
	table tr { height: 1px;}	

		table td {
			position: relative;
			height: 100%;
			vertical-align: middle;
		}
		@-moz-document url-prefix() {
			table td {white-space: nowrap;}
			table td:before {
				display: inline-block;
				height: 100%;
				width: 5px;
				background: green;
				content: '';
				vertical-align: middle;
			}
			.wrapper {
				white-space: normal;
				display: inline-block;
				vertical-align: middle;
				position: relative;
				margin-right: 8px;
				border: 1px solid #000;
			}
			td.through-one { display: block;}
		}

			td.through-one:after {
					background: #303;
					background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
					background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
					position: absolute;
					width: 20px;
					bottom: 30px;
					top: 20px;
					left: 0px;
					content: '';
					border-radius : 5px;
				}

			/* Дополнительные стили, не играющие роль */
			tr { border-top: 5px solid #F60;}
			.price { font: bold 14px "Trebuchet MS", Arial, Helvetica, sans-serif; color: #E76D13;}
			.content { overflow: hidden;}
			table td img { float: left; margin: 0 10px 0 30px;}


Как видно из рисунка, в нашей таблице неплохо работает вертикальное выравнивание и к тому же абсолютное позиционирование. Конечно же, не во всех ячейках, а через одну, так как в данной ситуации мы воспользовались вариантом для частных случаев. Но, давайте по порядку.
Каждая ячейка, по сути, состоит из двух, непереносящихся, за счёт white-space: nowrap, строчных блоков. Шаг с отменой переноса сделан для того, чтобы главный контейнер (.wrapper) при переполнении ширины не переносился на другую строку, под левый, вспомогательный инлайн-блок. Сам левый блок, для наглядности, я решил сделать 5px ширины и зелёного цвета, чтобы вы могли видеть, как всё это работает. А так же, чтобы не писать лишнюю разметку, я воспользовался псевдоэлементом :before. Этот элемент растянут на высоту ячейки, т.е. на 100% и выровнен по середине, по вертикали. Главный контейнер находится справа, и фактически содержит в себе всё основное содержимое ячейки, т.е. её контент. Он так же выровнен по вертикали, как и первый.
Так как наши блоки являются строчными, а первый растянут на 100%, то второй, главный блок, отлично взаимодействует с vertical-align и выравнивается по середине этой строки. При этом стоит заметить, что позиционирование и выравнивание работают отдельно и никак не мешают друг другу.

Да, естественно стоит учесть, что случай частный и поэтому этот приём не будет работать с ячейками, идущими подряд. Поэтому выставляю демонстрационный пример и иду дальше.

Эмуляция строк с двумя контейнерами

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

<table><tbody>
	<tr>
		<td>
			<div class="wrapper">
				<div class="wrapper-content">
					<img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</div>
		</td>
		<td>
			<div class="wrapper">
				<div class="wrapper-content">
					<img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</div>
		</td>
		<td>
			<div class="wrapper">
				<div class="wrapper-content">
					<img src="../img-td.jpg" />
					<div class="content">
						<div class="price">12000 рублей</div>
						<p>Описание товара, Описание товара, Описание товара</p>
					</div>
				</div>
			</div>
		</td>
	</tr>
	<tr>
	................
	</tr>
</tbody></table>
table { border-collapse: collapse; width: 100%;}

	/* Удивительное решение */
	table tr { height: 1px;}	

		table td {
			position: relative;
			height: 100%;
			vertical-align: middle;
		}
		@-moz-document url-prefix() {
			.wrapper {
				height: 100%;
				position: relative;
			}
			table td {white-space: nowrap;}
			.wrapper:before {
				display: inline-block;
				height: 100%;
				width: 5px;
				background: green;
				content: '';
				vertical-align: middle;
			}
			.wrapper-content {
				white-space: normal;
				display: inline-block;
				vertical-align: middle;
				position: relative;
				margin-right: 8px;
				border: 1px solid #000;
			}

		}

			.wrapper:after {
					background: #303;
					background: -webkit-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
					background: -moz-linear-gradient(top, rgba(231,109,19,1) 0%,rgba(0,0,0,1) 100%);
					position: absolute;
					width: 20px;
					bottom: 30px;
					top: 20px;
					left: 0px;
					content: '';
					border-radius : 5px;
				}

			/* Дополнительные стили, не играющие роль */
			tr { border-top: 5px solid #F60;}
			.price { font: bold 14px "Trebuchet MS", Arial, Helvetica, sans-serif; color: #E76D13;}
			.content { overflow: hidden;}
			table td img { float: left; margin: 0 10px 0 30px;}

А вот теперь всё в полном порядке, во всех ячейках. Но какой ценой? На самом деле небольшой. Всего лишь одним лишним контейнером мы заставили работать вертикальное выравнивание вместе с абсолютным позиционированием в ячейках таблицы. Почему же это работает? Всё просто. Теперь вместо ячейки мы используем контейнер (.wrapper), который растягивается на всю высоту первой. Внутри него мы уже проделываем все те же действия, которые мы совершали ранее в натуральных ячейках. Т.е. эмулируем пару строчных блоков, за счёт чего происходит нужное нам вертикальное выравниваиние. Ну, и position:absolute, работает так же идеально, за счёт этой же дополнительной обёртки.

По сути, вот это последнее решение, является почти: полностью работоспособным, кроссбраузерным (если заменить псевдоэлементы на настоящие, будет работать и в IE6-7) и законченным. Я сделал акцент на слове "Почти", потому что всё таки есть кое что, что мешает нам поставить точку в этой статье. Но об этом чуть позже…
Кстати, возможно кто-то заметил, на скриншоте виден странный баг. В некоторых ячейках не показывается левый, вспомогательный блок, который я специально покрасил в зелёный цвет. Он попросту исчезает, и происходит это в том случае, если высота обёртки (внутреннего контейнера) становится равной или выше высоты самой ячейки. Объяснения этому, к сожалению, найти так и не удалось, в связи с чем, я пришёл к выводу, что это обычный баг. В принципе, незачем бить тревогу, этот баг может и прячет контейнер, но, к счастью, на функциональность никак не влияет. Оставим его на совесть разработчиков Firefox.

Вертикальное выравнивание

Ложка дёгтя

К сожалению в каждой бочке мёда обязательно присутствует ложка дёгтя, и тут этой ложкой стал, как ни странно — IE9. При чём ни IE7 и ни IE8, а именно Internet Explorer 9. В этом браузере, почему-то, выключается чувствительность высоты ячейки, и именно в двух следующих случаях:
1. Если мы выставляем 100%-ую высоту любому блоку в ячейке, будь то статический или абсолютный. Он перестаёт чувствовать высоту и подстраивается под высоту самого контента в ячейке.
2. При одновременном задании абсолютному блоку таких свойств, как top и bottom. В таком случае элемент так же, как и в первом варианте, чувствует высоту только лишь контента, но не ячейки в целом.
Чтобы было ясно, о чём идёт речь, предоставлю скриншот.

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

Причём удивительно то, что, например, если задать отдельно одну из координат: top или bottom, то всё отлично, элемент с position: absolute оказывается в этих координатах в считанные секунды. Почему это происходит? Я, если честно — не понял. Но, эта неприятность, явно влияет на кроссбраузерность и заставляет искать решение, найти которое, к сожалению, мне не удалось, как я только не пытался (надеюсь кто нибудь пнёт в нужную сторону). Просто учитывайте этот момент во время вёрстки.

Резюме

В этой статье нам удалось найти уникальное решение, благодаря которому position: absolute в ячейках таблице стал кроссбраузерным, если не считать капризы IE9. Конечно же, не обошлось без грязных хаков и отдельных правил для Firefox, но зато результат оказался отличным. Если не брать в расчёт задачи, которые связанные с проблемами IE9 (или есть возможность сделать для него свои стили), то у нас есть полностью работающее решение, которое можно смело применять на любых проектах.

P.S. Это тоже может быть интересно:

28 комментариев

  1. Ну наконец то запустился, а то я уже отчаялся искать в рунете нормальные блоги по верстке, которые еще не умерли.
    С почином и успехов, Максим. Желаю чтоб желание писать не пропадало.

  2. Спасибо, друзья. Но, хочу вас предупредить, чтобы вы особо не настраивались на мою волну. К сожалению писать смогу не часто, из-за жуткой нехватки времени. Но, буду стараться.sad

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

    1. Спасибо за замечание. Есть некие пробемы, касаемо этого случая, но мы постараемся что нибудь придумать.

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

    Вобщем по поводу изменеия представления ячейки и позиционирования от неё, помню когдато тоже хотел решить задачу с выпадающим горизонтальным меню, тоесть оно как положенно красиво по всей ширине, резина, разное число пунктов, с рамками разными вроде этот http://onu.edu.ua/ru/abitur/ сайт был не помню уже точно. Короче justify не помню почему не получалось там применить нормально, нужна была таблица, но яж не говнокодер, сделал из списка таблицу дисплеем, и тут какраз на эту хрень попал с позиционированием (меню ведь выпадающее), и вот тут какраз (после интенсивного секса с мозгом) допёр до анонимных объектов, тоесть смысл был в том что по ховеру меняем display, делаем ячейку блоком, вложенный список (подменю) появляется и так как ячейка уже блок то оно без проблем позиционируется от неё, а вокруг ячейки на момент ховера анонимный объект и ничего не рушиться. Но не долго я радовался, тупорылый хром, нихрена не держит по псевдоклассу изменение display кроме block/none и наоборот, причём очень жестоко глючит, даже следующие правила не видит. А сразу делать все ячейки блоком (без ховера) нелья так как они попаду в один общий анонимный объект.

  5. > В этой статье нам удалось найти уникальное решение, благодаря которому position: absolute в ячейках таблице стал кроссбраузерным, если не считать капризы IE9.

    а до статьи было «уникальное решение, благодаря которому position: absolute в ячейках таблице стал кроссбраузерным, если не считать капризы FireFox»

    Забавно… какже люди не любят IE…

  6. Советую вместо таких длинных статей создавать видео уроки. Хоть тут и хтмл и ксс половину страницы занимает, все же, пока внимательно читаешь, глаза очень напрягаются. А при просмотре видео можно расслабиться, чуток отдалиться от экрана и кайфануть, если действительно качественная инфа. А так в целом у вас блог очень даже не плохой. ;) Продолжайте.

    1. Спасибо. Но, появилась идея делить большие статьи на несколько частей. Хотим вот попробовать такую практику :)

  7. Максим, а у меня в моем Firefox 12.0 ни один пример не работает. Видимо Firefox «усовершенствовал» их видение позиционирования в ячейках таблицы? :)

    1. Да, просто позиционирование от одной стороны в IE9 работает сносно. Проблемы там с растяжением от одной стороны до другой (top: 5px; bottom: 5px etc.).

    2. Да, и хочу вас сразу предупредить. Firefox от версии к версии постоянно чудит и решение предложенное в статье в нём то работает, то нет. Вот в 16-й версии вроде не работало, а в 17-й уже пашет. Так что будьте аккуратны:)

      upd: Вот о чём свидетельствует вот этот комментарий, например.

    1. Хм, разве это решение (с его плюсами, минусами, ограничениями и подводными камнями) не описано в середине статьи, под заголовком «Абсолютное позиционирование без контейнера»? ;)

  8. у вас сейчас цвет текста в полях с кодом такой же, как цвет фона этих областей — кода не видно.

    и еще: совсем недавно описанный баг был исправлен и теперь в FF его нет.

    1. Резонное замечание!

      Но, во-первых, статье уже пять лет. в ту пору еще не было повсеместных флексбоксов и табличное отображение было единственным универсальным способом визуально выровнять высоту ячеек напрямую. А во-вторых, описанные CSS-баги относятся именно к display:table-* и не зависят от тегов. И никуда не делись бы, если бы подобная задача возникла для бесспорной таблицы, так что знать «противоядие» в любом случае было полезно.

      А теперь, да, баг Firefox пофикшен и статья практически потеряла актуальность.

    1. Да, с border-collapse: collapse у Фокса по-прежнему есть нерешенные проблемы. Впрочем, новая спецификация, где это должно быть хоть как-то определено, еще очень сырая. Но можно ее подправить, чтоб для мозилловцев появился авторитетный источник!

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

        Честно говоря, потребность в margin-ах именно для ячеек (а не их содержимого) мне самому кажется сомнительной. Вот что порой раздражает — то, что нельзя увеличить border-spacing’и, например, между разными группами tbody…

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.