Измерение высоты текста в соответствии с правилами CSS - без рендеринга браузера - для использования с виртуализированным списком, чтобы заранее указать высоту

Я реализую чат-клиент в Electron (Chrome) и React. Наш главный приоритет - скорость. Таким образом, нам следует использовать компонент виртуализированного списка (также известный как "буферизованный рендеринг" или "рендеринг окна"). Мы исследовали, среди прочего, react-virtualized, response-window и react-infinite.

Общая проблема всех этих компонентов заключается в том, что при поддержке элементов списка переменной высоты высота должна быть известна заранее. Некоторые чаты очень длинные, а другие очень короткие, поэтому для нас это является проблемой. (Изображения и видео легко получить благодаря данным EXIF ​​и ffprobe).

Итак, мы столкнулись с проблемой измерения высоты, но при этом стараемся быть максимально производительными. Один из очевидных способов - поместить элементы в контейнер браузера вне области просмотра, выполнить измерения и затем отобразить список. Но это вредит нам с точки зрения требований к производительности. Программное обеспечение, такое как react-virtualized/CellMeasurer (которое больше не поддерживается первоначальным автором) и response-window, заставляют нас использовать этот метод, встроенный в библиотеку, но производительность несколько медленная, а также ненадежная. Похожая идея, которая могла бы быть более производительной, заключалась бы в использовании фонового окна браузера Electron для выполнения рендеринга и измерения, но моя интуиция подсказывает, что это будет не намного быстрее.

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

Моя текущая идея состоит в том, чтобы использовать библиотеку, такую ​​как string-pixel-width, для вычисления высоты строк, как только мы получим текстовые данные через наш API. По сути, библиотека использует этот фрагмент кода для создания карты ширины символов [*]. Затем, когда мы узнаем ширину каждого текста, мы отделяем каждую строку, когда она достигает максимальной вычисленной максимальной ширины строки, и, наконец, выводим высоту элемента списка с помощью количества строк. Это потребует немного алгоритмической возни из-за break-word, но есть библиотеки, которые помогут с этим - css-line-break кажется многообещающим.

[*] Нам пришлось бы немного изменить его, чтобы учесть все диапазоны символов Unicode, но это тривиально.

Некоторые варианты, которые я еще не полностью изучил, включают проект python weasyprint и проект facebook-yoga. Я открыт для ваших идей!

1 ответ

Решение

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

Текст холста электронов вычисляется так же, как и обычный текст, есть некоторые различия в рендеринге, особенно в отношении сглаживания, но это не влияет на расчет.

Вы можете получить TextMetrics из любого текста с помощью

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

// Set your font parameters
// Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/font
ctx.font = "30px Arial";

// returns a TextMetrics object
// Docs: https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics
const text = ctx.measureText('Hello world')

Это не включает разрывы строк и перенос слов, для этой функции я бы рекомендовал вам использовать текстовый пакет от pixijs, он уже использует этот метод. Кроме того, вы можете разветвить исходный код (лицензия MIT) и изменить его для дополнительной производительности, включив экспериментальные функции хрома TextMetrics в электронном виде и используя их.

Это можно сделать при создании окна

new BrowserWindow({
  // ... rest of your window config ...

  webPreferences: {
    experimentalFeatures: true
  }
})

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

это дополнительный параметр в webPreferences

webPreferences: {
  preload: path.join(__dirname, 'preload.js')
  experimentalFeatures: true
}

В этом сценарии у вас есть полный доступ к узлу, включая собственные модули узла, без использования вызовов IPC. Причина, по которой я не одобряю вызовы IPC для любого типа функции, которая вызывается несколько раз, заключается в том, что она медленная по своей природе, вам необходимо сериализовать / десериализовать, чтобы они работали. Поведение по умолчанию для электрона еще хуже, поскольку он использует JSON, за исключением того, что вы используетеArrayBuffers.

Другие вопросы по тегам