Я хочу рассказать о том, что такое font boosting в мобильных браузерах, к какой неожиданной проблеме он может привести при web-разработке и как с этой проблемой бороться.
Рассмотрим пример из реальной жизни:
Пример 1:
- Имеется вновь созданный
span
сdisplay: inline-block
.- Измеряем его ширину в пикселях через свойство
offsetWidth
.- Меняем его цвет.
- И, вдруг, в Google Chrome for Mobile, после изменения цвета ширина элемента резко увеличивается, переставая соответствовать той, что была измерена всего двумя строчками выше!
Показать код<!DOCTYPE html> <html> <head> <meta http-equiv = "content-type" content = "text/html; charset=utf-8" /> <title>Проблема с Font boosting в Google Chrome for Mobile</title> <script type = "text/javascript"> window.onload = function () { var spnSpan1 = document.getElementById ("span-1"); alert ("Ширина элемента до изменения цвета: "+ spnSpan1.offsetWidth +"px"); //59px spnSpan1.style.color = "red"; alert ("Ширина элемента после изменения цвета: "+ spnSpan1.offsetWidth +"px"); //186px } </script> </head> <body> <p> <span id = "span-1" style = "display: inline-block;">Элемент</span> </p> <!-- На странице должно быть достаточно текста. Если убрать этот абзац, глюк перестанет возникать. --> <p> abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc </p> </body> </html>
Смотреть пример on-line.
(Для просмотра примеров из этой статьи воспользуйтесь Google Chrome for Mobile или обычным Google Chrome в режиме эмуляции смартфона, например Apple iPhone или Samsung Galaxy Note II).
Причиной такого странного поведения, как раз, и является font boosting.
Что такое font boosting
Font boosting — это специальный прием, с помощью которого мобильные браузеры подгоняют размер шрифта под разрешение мобильного устройства. Этот прием нужен из-за того, что многие web-страницы, сверстанные в расчете на десктопные браузеры, содержат текстовые элементы, ширина которых превышает ширину мобильного экрана. Для просмотра этих элементов посетитель вынужден либо использовать горизонтальную прокрутку, либо вписать элемент в размеры экрана, уменьшив масштаб страницы. Однако, при уменьшении масштаба уменьшается также и размер шрифта, делая текст порой совершенно нечитаемым. Так вот, font boosting специально увеличивает размер шрифта, так, чтобы после вписывания блока в ширину экрана, этот размер шрифта соответствовал изначально задуманному.
Степень увеличения размера шрифта при font booting'е зависит от ширины элемента — чем шире элемент, тем сильнее его надо уменьшить, чтобы вписать в размеры экрана, и, соответственно, тем больше надо увеличить размер шрифта для компенсации этого уменьшения.
Проблемы font boosting в Google Chrome for Mobile
Реализация font boosting в Google Chrome for Mobile имеет две особенности, которые могут привести к сложнообнаружимым ошибкам при web-разработке:
- Font boosting элемента происходит не сразу после его создания, а после ближайшей «перерисовки» (reflow) страницы. В свою очередь, reflow, как известно, происходит после загрузки страницы, после завершения модифицирующего страницу скрипта, а также при обращении к свойствам, завязанным на геометрию страницы, например
offsetWidth
. - Font boosting элемента с
display: inline-block
происходит не просто после reflow страницы, а после reflow и изменения какого-нибудь свойства самого элемента (например цвета).
Проиллюстрируем вторую особенность на примере:
Пример 2:
Существует 4
span
'а, у 1–3 из которых заданоdisplay: inline-block
, а у 4-го нет. В результате, у 4-го элемента размер увеличивается сразу после загрузки страницы, а у элементов 1–3 — только после изменения их цвета.Показать код<!DOCTYPE html> <html> <head> <meta http-equiv = "content-type" content = "text/html; charset=utf-8" /> <title>Проблема с Font boosting в Google Chrome for Mobile</title> <script type = "text/javascript"> window.onload = function () { /* Вновь созданные элементы */ var spnSpan1 = document.getElementById ("span-1"); var spnSpan2 = document.getElementById ("span-2"); var spnSpan3 = document.getElementById ("span-3"); var btnGo = document.getElementById ("btnGo"); /* Изменим цвет элементов, и их размер неожиданно увеличится */ btnGo.onclick = function () { spnSpan1.style.color = "red"; spnSpan2.style.color = "red"; spnSpan3.style.color = "red"; } } </script> </head> <body> <div> <input type = "button" id = "btnGo" value = "Изменить цвет!" /> <!-- У этих элементов есть свойство "display: inline-block", поэтому сначала они будут маленькими, но после изменения цвета их размер увеличится! --> <p> <span id = "span-1" style = "display: inline-block;">Элемент 1</span> </p> <p > <span id = "span-2" style = "display: inline-block;">Элемент 2</span> </p> <p> <span id = "span-3" style = "display: inline-block;">Элемент 3</span> </p> <!-- У этого элемента нет свойства "display: inline-block", поэтому он сразу после отрисовки страницы будет большим --> <p> <span id = "span-4">Элемент 4</span> </p> <!-- На странице должно быть достаточно текста. Если убрать этот абзац, глюк перестанет возникать. --> <p> abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc </p> </div> </body> </html>
Смотреть пример on-line.
Зная об этих особенностях, можно понять причину странного поведения Google Chrome for Mobile в Примере 1:
- Когда мы измеряем
offsetWidth
вновь созданного элемента, мы получаем значение до font boosting. - В то же время, это измерение активирует reflow страницы.
- Наконец, изменение цвета элемента запускает у него font boosting, и размер шрифта увеличивается. А вместе с увеличением размера шрифта увеличивается и
offsetWidth
, становясь заметно больше измеренного двумя строчками выше.
Что делать?
Существует два способа предотвратить подобные скачки размера шрифта:
Способ 1 —
отменить font boosting. Для этого надо установить ширину web-страницы (а, точнее, ширину ее вьюпорта) равной ширине экрана, добавив тег:
<meta name = "viewport" content = "width=device-width, initial-scale=1">
В результате, дальнейшее уменьшение масштаба страницы станет невозможным, и font boosting, призванный компенсировать это уменьшение, естественным образом не запустится.
Пример 3
Показать код<!DOCTYPE html> <html> <head> <meta http-equiv = "content-type" content = "text/html; charset=utf-8" /> <!-- Делаем ширину страницы равной ширине экрана --> <meta name = "viewport" content = "width=device-width, initial-scale=1"> <title>Решение№1 проблемы с Font boosting в Google Chrome for Mobile</title> <script type = "text/javascript"> window.onload = function () { var spnSpan1 = document.getElementById ("span-1"); /* В резальтате, font boosting не возникнет, и размер элемента при изменении его цвета не будет меняться. */ alert ("Ширина элемента до изменения цвета: "+ spnSpan1.offsetWidth +"px"); //186px spnSpan1.style.color = "red"; alert ("Ширина элемента после изменения цвета: "+ spnSpan1.offsetWidth +"px"); //186px } </script> </head> <body> <p> <span id = "span-1" style = "display: inline-block;">Элемент</span> </p> <p> abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc </p> </body> </html>
Смотреть пример on-line.
Однако, данный способ не всегда применим, особенно если мы разрабатываем javascript-библиотеку и не имеем полного контроля над конечной страницей.
Способ 2 —
принудительно спровоцировать font boosting сразу после создания элемента. Для этого надо:
- Вызвать reflow страницы. Для вызова reflow можно, например, измерить
document.body.offsetWidth
. - Если элемент имеет
display: inline-block
, то, также, изменить его цвет, а затем вернуть обратно.
В результате, к тому моменту, когда мы начнем проводить с элементом какие-то содержательные действия, скачок размера шрифта у него уже состоится и не будет нам мешать.
Пример 4
Показать код<!DOCTYPE html> <html> <head> <meta http-equiv = "content-type" content = "text/html; charset=utf-8" /> <title>Решение№2 проблемы с Font boosting в Google Chrome for Mobile</title> <script type = "text/javascript"> window.onload = function () { var spnSpan1 = document.getElementById ("span-1"); /* Провоцируем font boosting сразу после создания элемента. */ doReflow (); doFontBoosting (spnSpan1); /* В результате, в дальнейшем размер его шрифта уже не будет меняться. */ alert ("Ширина элемента до изменения цвета: "+ spnSpan1.offsetWidth +"px"); //186px spnSpan1.style.color = "red"; alert ("Ширина элемента после изменения цвета: "+ spnSpan1.offsetWidth +"px"); //186px } function doReflow () { document.body.offsetWidth; } function doFontBoosting (elElement) { var strColor = elElement.style.color; elElement.style.color = (strColor == "red" ? "blue" : "red"); elElement.style.color = strColor; } </script> </head> <body> <p> <span id = "span-1" style = "display: inline-block;">Элемент</span> </p> <p> abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc </p> </body> </html>
Смотреть пример on-line.
Заключение
Надеюсь, что если кто-то из хабралюдей вдруг столкнется с непредсказуемыми скачками размеров шрифта в мобильных браузерах, эта заметка позволит сэкономить определенное количество времени и нервов. Make code, not war.
Автор: Alik_Kirillovich