95 lines
4.0 KiB
Markdown
95 lines
4.0 KiB
Markdown
# 第九章:把它接进界面和虚拟列表
|
|
|
|
### 9.1 一个完整的浏览器模块例子
|
|
|
|
先在你的前端项目中安装:
|
|
|
|
```sh
|
|
npm install @chenglou/pretext
|
|
```
|
|
|
|
下面是供 Vite 等支持 npm 模块导入的环境使用的 JavaScript 模块。直接打开一个 HTML 文件并不能解析这个包名。
|
|
|
|
```js
|
|
import { prepare, layout } from '@chenglou/pretext'
|
|
|
|
const text = 'Hello 世界,今天一起读源码。'.repeat(8)
|
|
const font = '16px "Helvetica Neue", "PingFang SC", Arial, sans-serif'
|
|
const lineHeight = 24
|
|
|
|
document.documentElement.lang = 'zh-CN'
|
|
await document.fonts.load(font, text)
|
|
|
|
const prepared = prepare(text, font)
|
|
const block = document.createElement('div')
|
|
block.textContent = text
|
|
Object.assign(block.style, {
|
|
font,
|
|
lineHeight: lineHeight + 'px',
|
|
overflowWrap: 'break-word',
|
|
wordBreak: 'normal',
|
|
whiteSpace: 'normal',
|
|
lineBreak: 'auto',
|
|
letterSpacing: '0px',
|
|
wordSpacing: '0px',
|
|
fontKerning: 'auto',
|
|
textWrap: 'wrap',
|
|
hyphens: 'manual',
|
|
textRendering: 'auto',
|
|
boxSizing: 'content-box',
|
|
padding: '0',
|
|
border: '0',
|
|
})
|
|
document.body.appendChild(block)
|
|
|
|
function setContentWidth(width) {
|
|
const predicted = layout(prepared, width, lineHeight)
|
|
block.style.width = width + 'px'
|
|
return predicted
|
|
}
|
|
|
|
const predicted = setContentWidth(320)
|
|
console.log('预测高度:', predicted.height)
|
|
console.log('实际高度:', block.getBoundingClientRect().height)
|
|
|
|
// 模拟容器变宽:复用相同的 prepared。
|
|
setContentWidth(480)
|
|
```
|
|
|
|
这个例子最后的 DOM 高度读取是开发期核对。生产中如果每次布局都再读一次 DOM,仍会承担那次读取可能触发的工作。示例也假设没有其他 CSS 规则覆盖这些值,所用字体在目标设备上可用;跨平台项目应配置自己的字体。
|
|
|
|
### 9.2 让模型与显示使用相同输入
|
|
|
|
`layout(prepared, 320, 24)` 中的 320 是内容宽度。如果外框总宽 320px、左右各有 12px padding,再加边框,真正给文字使用的宽度就小于 320px。
|
|
|
|
可以由应用维护一个尺寸模型:先算出内容宽度,把同一个数同时传给 Pretext 和渲染层。这样可以减少“CSS 用一个值,JS 猜另一个值”的误差。
|
|
|
|
准备前等待具体文字所需字体加载很重要。只有 `document.fonts.ready` 不一定覆盖尚未使用、由 unicode-range 分片加载的字体;因此例子把 text 也传给 `document.fonts.load()`。
|
|
|
|
### 9.3 虚拟列表从哪里获得收益
|
|
|
|
假设有 10,000 条消息,屏幕上只显示 20 条:
|
|
|
|
```text
|
|
每条消息保留 prepared
|
|
→ 按当前内容宽度求各条高度
|
|
→ 累计高度,确定每条消息顶部的位置
|
|
→ 按滚动位置找可见范围
|
|
→ 只挂载可见消息与少量缓冲区
|
|
```
|
|
|
|
Pretext 解决的是“没挂载的文字怎样求高度”。累计位置、查找可见范围、滚动锚定、图片高度、选择与无障碍行为,仍由应用或虚拟列表库负责。
|
|
|
|
窗口变宽时,每条消息通常仍需要重新布局。因此大量消息的整表 resize 成本不会凭空消失。准备与布局分离,让这次更新的单位成本更低。
|
|
|
|
### 9.4 编辑与富文本如何处理
|
|
|
|
长文本编辑时,可以按段落保留 prepared,只重新准备被编辑影响的段落;有跨段结构变化时,应用还要更新相应块。
|
|
|
|
混合字体、提及标签和行内图片使用 `@chenglou/pretext/rich-inline`。这个版本把富文本作为一个段落分析,保留 item 与分段的映射,然后复用布局基础设施,而不是把每个 span 完全独立换行。
|
|
|
|
富文本还要考虑垂直尺寸:字体大小、上升部与下降部、行内盒子高度都可能影响真实行高。不要直接把所有富文本的行数乘一个固定值,当成任意 CSS 的精确高度。可继续读项目自己的 [Markdown 聊天案例说明](https://github.com/chenglou/pretext/blob/0d102a5cd933611e86a7e60bda7b4baa5995c6b0/pages/demos/markdown-chat.md)。
|
|
|
|
本章检查:Pretext 是否已经替你完成虚拟列表?没有,但它提供了此前很难在挂载前获得的文本几何信息。
|
|
|