Ширина столбцов таблицы или когда врут браузеры

в 15:50, , рубрики: Firefox, баги, Веб-разработка, верстка, метки: , ,
Предыстория

История начинается с одного древнего проекта с web-интерфейсом написанным ещё под IE5-6. Разумеется этот мамонт под новыми версиями IE работает только в quirks mode, под остальными браузерами даже отрисовывается с трудом а про работоспособность мечтать и не приходится.
Одним светлым днём с небес прилетел глас начать постепенно переписывать это всё на современные браузеры и работа закипела.
99% системы представляли из себя реестры в виде таблиц и форм отдельной карточки из этого реестра. Заголовок таблицы должен быть фиксирован. В старой версии это делалось какими-то специфичными костылями c position которые не работали уже в IE7. jQuery уже был подключён, плагин для фиксированного заголовка таблицы гуглится легко. Не поддерживает заголовки с несколькими строками и различной комбинацией col и rowspan'ов? Ну ладно, можно и самому поработать немного, всё равно лезть в код плагина и адаптировать его под специфичную обёртку таблиц.
Казалось бы всё хорошо, но время от времени стали возникать артефакты в виде уползания столбцов на 1 пиксел, местами сдвиг пропадал или накапливался до 3-4 пикселов. Причём в Chrome данный глюк не наблюдался.
image

Тщетные попытки исправить ситуацию

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

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

Дальше я решил проверить ширину столбцов после отрисовки и форматирования таблицы-заголовка. Вдруг где-то была ошибка в разметке и ширина таблицы могла меняться после отрисовки нового элемента, но значения ширины оказались идентичны до и после. Никаких ухищрения со стилями вроде border-box и display нужного результата также дали — таблицы всё также различались.

Скандалы, интриги и расследования

Совсем уже отчаявшись взял в руки калькулятор, открыл firebug и начал складывать ширину каждого из столбцов в попытке найти где именно начинает уезжать таблица. И… firebug сказал что ширина у них всех одинаковая! Как же так? Они даже визуально разные. Может быть проблема в firebug'е в закладке с разметкой объекта? Смотрю в DOM'е — там тоже самое значение в clientWidth, встроенный инспектор в FF выдаёт аналогичные значения.
Глаз может и можно обмануть, а вот paint вряд ли. Копирую скрин окна браузера в редактор и начинаю считать ширину столбцов. Дохожу до первого кривого столбца и paint выдаёт ширину на 1 пиксел меньше того что говорит браузер! Возвращаюсь назад на страничку. Все инструменты браузера и jQuery мамой клянутся что ширина столбца 473 пиксела, но paint твёрдо настаивает что на самом деле 472. Сразу вспомнился монолог Задорнова про рулетку склеенную по середине. Неужели тёщи добрались и до разработчиков? К слову в IE ситуация точно повторяла то что происходит в IE. Заговор?
image
Следующий час я провожу в гугле в попытках найти существующие баги подсчёта ширины столбцов или какие-то особенности clientWidth или функции .width(). И безрезультатно. Всё что удаётся найти это либо про дробные значения в css либо явные косяки авторов разметки.
Напился (с)

Должен же быть способ узнать правильную ширину столбца, как-то таблица же рисуется браузером и ничто никуда не заползает. Ничего не остаётся кроме перебора возможных различий в свойствах ячеек. Вывожу свойства столбца из DOM исходной таблицы и сгенерированной плагином. Сравниваю. Вроде всё совпадает… кроме offsetLeft. Как же так? Вставляем костыль с расчётом ширины по смещению, для последней ячейки будем использовать обычную ширину:

this.nextSibling.offsetLeft - this.offsetLeft = 472

И вот теперь таблицы стали одинаковыми во всех имеющихся у меня браузерах.

Что же происходит

Разметка таблицы не содержит ширины столбцов и браузер автоматически подстраивает ширину столбцов по своему желанию опираясь на содержимое таблицы. Где-то рассчитанное значение ширины получается дробным, и тут-то по всей видимости начинаются проблемы реализации.
Так как дробную ширину нарисовать нельзя — значение округляется, и это округлённое значение подставляется в DOM. При определённых соотношения ошибка округления может накопиться и общая ширина таблицы вырастет или уменьшится. Если верить статье habrahabr.ru/post/31392/ то FF старается вписать содержимое ровно по размеру родителя и уменьшает значения некоторых столбцов на 1 пиксел чтобы они не превысили ширину таблицы, offsetLeft при этом изменяется у смещённых столбцов, а вот clientWidth нет! Поэтому при попытке создать копию заголовка таблицы сценарию возвращается не уменьшенная (или увеличенная) на 1 пиксел ширина и столбцы расползаются.
Другого варианта я не вижу.
Аналогичная ситуация и в IE, только там инспектор показывает дробное значение ширины столбца, в clientWidth округлённое значение, а отрисовано всё равно на 1 пиксел меньше. Тот же диагноз, только инспектор не врёт.

Дабы исключить ситуацию что какая-то ошибка в вёрстке страницы попробуем воспроизвести баг в тепличных условиях:

Скрытый текст

<!DOCTYPE html>
<html>
<head>
    <script src="calc/jquery.js"></script>
    <script>
        $(document).ready(function(){
            $(window).resize(function() {
                $('#tr > *').each(function() {
                    var width1 = this.clientWidth;
                    var width2;

                    if ($(this).next('td').length) {
                        width2 = $(this).next('td')[0].offsetLeft - this.offsetLeft;
                    }
                    console.log(width1, width2);
                });
            });
        });
    </script>
</head>
<body>
    <table width="100%" border="0" cellpadding="0" cellspacing="0">
        <tr id="tr">
            <td>1234556</td>
            <td>123455644</td>
            <td>12345</td>
            <td>12345565645</td>
            <td>123455643</td>
            <td>12345563453433</td>
            <td>12345565645</td>
            <td>123455643</td>
            <td>12345563453433</td>
        </tr>
    </table>
</body>
</html>

И действительно, у меня в FF при изменении ширины окна width1 и width2 иногда различаются на 1 пиксел. Причём различие может быть как в большую так и в меньшую сторону что исключает применение разных способов округления при заполнении DOM и при отрисовке страницы, а действительно проблема где-то на этапе “уплотнения” столбцов таблицы.

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

Может я не прав в данной ситуации и багрепорт создавать не надо? Такое поведение вполне ожидаемо и в моём случае нужно использовать такой костыль для корректного выравнивания ширины столбцов? Хотелось бы услышать ваше мнение.

Автор: aspirineilia

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js