Измерение высоты текста в соответствии с правилами 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
.