#平滑化
流式文本通常是成批到达的:有时只收到几个字,有时一次收到一整段。平滑化把接收和显示分开,让内容按更连贯的节奏出现在页面上。
#看见缓冲区
下面的演示模拟大小不同的文本片段陆续到达。正常颜色是已经显示的内容,另一种颜色是已收到、仍在等待显示的缓冲内容。实际应用中,缓冲区里的文字暂时不可见。
新片段到达时,缓冲区先增长,显示边界再逐步跟上。可以暂停观察两者的位置,或重新播放,比较每次收到内容时的变化。
#弹簧如何调整节奏
内置的 spring 调度器用显示位置和待显示内容之间的距离调整速度。积压内容较多时,输出加快;接近末尾时,速度逐步收敛。它保留前一帧的速度,让后续更新衔接起来。
调度器按经过的时间计算进度,再把不足一个字符的部分留到下一次更新。这样,文字可以在两次网络更新之间继续显示,也不需要为每个字符设置固定延时。上面的演示使用与 Smooth mapper 相同的 SpringSmoothScheduler。
#在应用中使用
选择对应框架查看代码。Core 示例中的 mappers 传入 Core 输入,只生成内容与效果标记,视觉呈现由渲染器完成;组件已内置对应渲染器。
开启 streaming 就会使用默认的弹簧平滑化:
import type { MapperPluggable } from '@fluxdown/core';
import { Smooth } from '@fluxdown/core-plugins/plugins/mapper/smooth';
import { Scale } from '@fluxdown/core-plugins/plugins/mapper/scale';
import { MutableState, toClosure } from 'stative';
const isStreaming = MutableState.of(true);
const active = toClosure(isStreaming);
const mappers: MapperPluggable[] = [
[Smooth, { enabled: active }],
[Scale, { enabled: active }],
];import { Fluxdown } from 'fluxdown';
type MessageProps = {
text: string;
isStreaming: boolean;
};
export function Message({ text, isStreaming }: MessageProps) {
return <Fluxdown streaming={isStreaming} text={text} />;
}<script setup lang="ts">
import { Fluxdown } from '@fluxdown/vue';
defineProps<{ text: string; isStreaming: boolean }>();
</script>
<template>
<Fluxdown :streaming="isStreaming" :text="text" />
</template><script lang="ts">
import { Fluxdown } from '@fluxdown/svelte';
type MessageProps = {
text: string;
isStreaming: boolean;
};
let { text, isStreaming }: MessageProps = $props();
</script>
<Fluxdown streaming={isStreaming} {text} />import { Component, input } from '@angular/core';
import { Fluxdown } from '@fluxdown/angular';
@Component({
selector: 'app-message',
imports: [Fluxdown],
template: `<fluxdown [streaming]="isStreaming()" [text]="text()" />`,
})
export class Message {
readonly text = input.required<string>();
readonly isStreaming = input(false);
}import { Fluxdown } from '@fluxdown/react-native';
type MessageProps = {
text: string;
isStreaming: boolean;
};
export function Message({ text, isStreaming }: MessageProps) {
return <Fluxdown streaming={isStreaming} text={text} />;
}import { Fluxdown } from '@fluxdown/lynx';
type MessageProps = {
text: string;
isStreaming: boolean;
};
export function Message({ text, isStreaming }: MessageProps) {
return <Fluxdown streaming={isStreaming} text={text} />;
}import { Fluxdown } from '@fluxdown/solid';
type MessageProps = {
text: string;
isStreaming: boolean;
};
export function Message(props: MessageProps) {
return <Fluxdown streaming={props.isStreaming} text={props.text} />;
}import { Fluxdown } from '@fluxdown/taro';
type MessageProps = {
text: string;
isStreaming: boolean;
};
export function Message({ text, isStreaming }: MessageProps) {
return <Fluxdown streaming={isStreaming} text={text} />;
}也可以显式指定调度器,并配合动画效果使用:
import type { MapperPluggable } from '@fluxdown/core';
import {
RafSmoothTicker,
Smooth,
SpringSmoothScheduler,
} from '@fluxdown/core-plugins/plugins/mapper/smooth';
import { Shad } from '@fluxdown/core-plugins/plugins/mapper/shad';
const mappers: MapperPluggable[] = [
[
Smooth,
{
enabled: true,
ticker: RafSmoothTicker,
scheduler: SpringSmoothScheduler,
},
],
[Shad, { enabled: true }],
];import { Fluxdown } from 'fluxdown';
type MessageProps = {
text: string;
};
export function Message({ text }: MessageProps) {
return (
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
text={text}
/>
);
}<script setup lang="ts">
import { Fluxdown } from '@fluxdown/vue';
defineProps<{ text: string }>();
</script>
<template>
<Fluxdown
:streaming="{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}"
:text="text"
/>
</template><script lang="ts">
import { Fluxdown } from '@fluxdown/svelte';
type MessageProps = {
text: string;
};
let { text }: MessageProps = $props();
</script>
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
{text}
/>import type { StreamingConfig } from '@fluxdown/angular';
import { Component, input } from '@angular/core';
import { Fluxdown } from '@fluxdown/angular';
@Component({
selector: 'app-message',
imports: [Fluxdown],
template: `<fluxdown [streaming]="streaming" [text]="text()" />`,
})
export class Message {
readonly text = input.required<string>();
readonly streaming: StreamingConfig = {
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
};
}import { Fluxdown } from '@fluxdown/react-native';
type MessageProps = {
text: string;
};
export function Message({ text }: MessageProps) {
return (
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
text={text}
/>
);
}import { Fluxdown } from '@fluxdown/lynx';
type MessageProps = {
text: string;
};
export function Message({ text }: MessageProps) {
return (
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
text={text}
/>
);
}import { Fluxdown } from '@fluxdown/solid';
type MessageProps = {
text: string;
};
export function Message(props: MessageProps) {
return (
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
text={props.text}
/>
);
}import { Fluxdown } from '@fluxdown/taro';
type MessageProps = {
text: string;
};
export function Message({ text }: MessageProps) {
return (
<Fluxdown
streaming={{
smooth: { enabled: true, ticker: 'raf', scheduler: 'spring' },
effect: 'shad',
}}
text={text}
/>
);
}text 始终传入已经收到的完整内容。将 streaming 设为 false 后,剩余缓冲会立即显示;如果希望保留收尾过程,可以让平滑化继续开启。
Core 使用同一个 Smooth mapper,输出经过截取的内容块,渲染层无需自己安排逐字更新。接入方式见 Core 状态与更新,完整选项见各框架的配置文档:React、Vue、Svelte、Angular、React Native、Lynx、Solid、Taro。
Core 示例用 MutableState 保存接收状态;完成时调用 isStreaming.next(false),销毁 Core 后再调用 active.destroy() 和 isStreaming.destroy() 清理外部状态。