Почему Text.splitText() влияет на макет?

Скажем, у нас есть абзац на нашей странице с одним блоком текста.

<p>laborum beatae est nihil, non hic ab, deserunt repellat quas. Est molestiae ipsum minus nesciunt tempore voluptate laboriosam</p>

DOM-wise, структура:

HTMLParagraphElement
  Text [laborum beatae est nihil...]

Теперь мы разбили его (с Text.splitText()) дважды, чтобы отделить фрагмент "deserunt repellat quas. Est". Структура становится:

HTMLParagraphElement
  Text [laborum beatae est nihil...]
  Text [deserunt repellat quas. Est]
  Text [ molestiae ipsum minus nesciunt...]

Пока эта операция влияет на DOM, она никогда не меняет ее на уровне элемента (Text! == Element), поэтому я не ожидал визуальных изменений.

Тем не менее splitText() влияет на макет, вызывая как ретрансляцию, так и перерисовку во всех проверенных браузерах (Chrome 60, Firefox 55, Edge 14 - все в ОС Windows 10). То же самое происходит, когда мы вызываем ParagraphElement.normalize(), уменьшая количество узлов Text до 1; снова запускаются как ретрансляция, так и перерисовка.

Существует неприятный побочный эффект, который можно увидеть в этой демонстрации. Если вы проверите слова рядом с "quas". Est ', вы видите, что они фактически меняют позиции!

Он четко виден в Firefox и гораздо более тонкий (но еще и различимый) в Chrome. К моему удивлению, в Edge не было такого "танец слова".

Причина, по которой это важно, показана в этой демонстрации (типа) механизма выбора подгонки. Эта конкретная версия не работает в Firefox (поддержка caretRangeFromPoint еще не создана - argh!), Но даже с "point2dom", переустановленным на caretPositionFromPoint, выделенный текст переместится туда - столько же в Chrome или, что еще хуже. Опять же, это похоже на работу в Edge.

Итак, на самом деле меня в основном интересуют как понимание причин, так и поиск обходных решений.

Здесь анимированный gif, показывающий, как первая демо воспроизводится в Chrome (я просто запускаю щелчок в интервале)

, когда слова идут в...

Трепет здесь тонкий, но все же можно наблюдать по всем словам. Я особенно озадачен тем, почему i в molestiae трясет, так как окружающие буквы, похоже, остаются там, где они есть.

И все хуже (хуже) с менее распространенными шрифтами и большим количеством текста, например, в демонстрации выбора.

Переключение на font-family:monospace не решило этого, но сделало это, казалось бы, хуже:

введите описание изображения здесь

Переключение font-kerning в none тоже не помогло.

ОБНОВЛЕНИЕ: В трекере Blink зарегистрирован вопрос .

Ответы

Ответ 1

О ретрансляции/перерисовке это следует ожидать, так как текстовые узлы также являются узлами DOM... Не элементы DOM, но браузеру необходимо пересмотреть макет, даже если вы ожидаете, что он останется прежним, они возможно, придется двигаться. Возможно, из-за кернинга.

Теперь, почему разделение текста вызывает какое-то движение? То, что я ожидаю, - это то, что браузеры рисуют текстовые части отдельно. Две соседние буквы обычно имеют пространство, которое может быть уменьшено шрифтом в зависимости от букв, например, "WA", конец W находится выше начала A, который называется kerning (спасибо Ismael Miguel). Когда текстовые узлы рисуются отдельно, каждый из них должен заканчиваться до следующего запуска, поэтому он может создавать большее пространство между этими буквами, поскольку он предотвращает кернинг.

Извините, пробел между буквами имеет имя, но я забыл...

.one {
  background-color: #FF9999;
}

.two {
  background-color: #99FF99;
}

body {
  font-size: 40px;
}

div>span {
  border: 1px solid black;
}
<div><span>AW</span> - in the same DOM node.</div>
<div><span><span>A</span><span>W</span></span> - in two different nodes</div>
<div><span><span class="one">A</span><span class="two">W</span></span> - in two different nodes, colored</div>

Ответ 2

tl: dr Версия

splitText() может быть действием, вызывающим изменение, но изменение на самом деле вызвано обновлением dom, проходящим через текст оправдание двигатель. Измените text-align: justify; на text-align: left;, чтобы понять, что я имею в виду.

Добавление

Это обрабатывает слова, перемещающиеся вокруг. Что касается сдвига букв при выключении оправданий, это немного сложнее определить количественно, но это, по-видимому, является порогом округления, который пересекается.

Полный ответ

Соображения в обосновании текста

Обоснование текста сложнее реализовать, если не сказать больше. Для упрощения, есть три основных соображения:

  • Точность
  • Эстетика
  • Скорость

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

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

Разница в реализации браузера

spec несколько расплывчато на тему оправдания, говоря такие вещи, как:

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

и

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

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

Пример

Современный механизм обоснования текста более сложный, чем может быть разумно объясняется в пространстве, которое мы здесь имеем. Добавьте, что они постоянно изменяются до найти лучший баланс между основными соображениями и тем, что я пишу здесь все равно будет устаревшим в несколько наносекунд, я собираюсь использовать очень старый (и гораздо более простой) алгоритм выравнивания текста, чтобы продемонстрировать, как движок может возникнуть затруднение в последовательном выполнении этой ситуации.

Предположим, что у нас есть строка 'Lorem ipsum dolor sit amet, consectetur.', и мы может содержать 35 символов на линии. Поэтому позвольте использовать этот алгоритм обоснования:

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

Используя этот алгоритм:

  • Разделить строку

    'Lorem ipsum dolor sit amet, consectetur.' => 
        ['Lorem','ipsum','dolor','sit','amet,'] & 'consectetur.'
    
  • С 23 символами ширины текста и доступными 35 символами пространства мы добавляем 3 пробела между каждым словом (я в четыре раза увеличиваю пространство для выделения, что будет важно позже)

    -------------------------------------------------------------------------
    |Lorem            ipsum            dolor            sit            amet,|
    |consectetur.                                                           |
    -------------------------------------------------------------------------
    

Это быстро, потому что мы можем рисовать все слева направо, не нуждаясь в отступлении, и нам не нужно смотреть вперед.

Если мы запустим textSplit на этом и эффективно превратим его в массив:
['Lorem ipsum dolor ','sit',' amet, consectetur.']
Поэтому нам нужно будет изменить наши правила, чтобы изменить правило 2 для работы через каждый строка в массиве, следуя тем же правилам, что и раньше.

  • Разделите строки, обратите внимание, что перед аметом есть пробел, поэтому граница слова не поймает его

    `['Lorem ipsum dolor ','sit',' amet, consectetur.']` => 
        ['Lorem','ipsum','dolor','sit',' amet,'] &  'consectetur.'
    
  • С 24 символами ширины текста и доступными 35 символами пространства, добавим 2.75 пробелов между каждым словом (снова четыре раза в пространстве). Дополнительное пространство в строке amet также рисуется.

    -------------------------------------------------------------------------
    |Lorem           ipsum           dolor           sit               amet,|
    |consectetur.                                                           |
    -------------------------------------------------------------------------
    

Если мы посмотрим на сторону двух линий, мы увидим разницу.

  -------------------------------------------------------------------------
a |Lorem            ipsum            dolor            sit            amet,|
b |Lorem           ipsum           dolor           sit               amet,|
  -------------------------------------------------------------------------

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

Наш набор правил очень прост, поэтому мы могли бы очень легко решить эту проблему.

Подумайте, насколько сложна эта отладка, когда у вас есть обоснование двигатель, который должен поддерживать:

  • Другие (настраиваемые) свойства css для обоснования
  • Несколько языков и все их правила
  • Шрифты, которые имеют:
    • Переменные ширины символов
    • Показатели кернинга
    • которые не обязательно совпадают с пикселем
  • Элементы Inline (и встроенного блока), которые имеют собственный стиль
  • ... on и on

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

В любом случае, все это, чтобы сказать

Имейте в виду, что вы, по сути, меняете дом и заставляете весь блок для повторной рендеринга в результате. Учитывая количество факторов, это очень трудно ожидать, что две разные структуры dom всегда будут делать то же самое.

Добавление

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

Опять же, чтобы повысить скорость, цифры часто округляются до передачи их на GPU для рендеринга.

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

Предположим, что последовательность рисования символов:

  • Запустите контейнер
  • Добавить смещение
  • Нарисуйте символ в этом месте, округленный до ближайшего пикселя
  • Обновите смещение, добавив неровную ширину символа.

Символы с шириной:

10.2 10.3 10.4 10.2 10.6 11 8.9 9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5

Начиная с точки 0, нарисовать при:

0    10   21   31   41   52 63  72  82  89  98  108  119  129  140  151

Но что, если вы дойдете до конца node? Ну что ж, легко, просто запустите новый node в следующей позиции и двигайтесь дальше?

Символы с шириной:

10.2 10.3 10.4 10.2 10.6 11 8.9|9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5

Начиная с точки 0, нарисовать при:

0    10   21   31   41   52 63  72  82  89  99  108  119  129  140  151

Несмотря на то, что базовые номера отличаются друг от друга, расположение рендеринга остается неизменным для каждого местоположения, кроме 11-го символа из-за округления.

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