Gemini-like Chatbot Scroll
The example below simulates a conversation with a chatbot. The scroll behavior simulates the Gemini chatbot, where each new question is scrolled to the top, so that a space is preallocated for the answer. The streaming response is simulated with setInterval
.
Key Points
- Each question gets aligned to the top of the viewport when it is sent.
- The
data.map
method is used to stream the incoming response. - The
autoscrollToBottomBehavior
is set tosmooth
to animate the scroll when the streaming response scroll beyond the visible area.
Live Example
Live Editor
interface Message { key: string; text: string; user: "me" | "other"; } let idCounter = 0; function randomMessage(user: Message["user"]): Message { return { user, key: `${idCounter++}`, text: randTextRange({ min: user === "me" ? 20 : 100, max: 200 }), }; } const ItemContent: VirtuosoMessageListProps<Message, null>["ItemContent"] = ({ data, }) => { const ownMessage = data.user === "me"; return ( <div style={{ paddingBottom: "2rem", display: "flex" }}> <div style={{ maxWidth: "80%", marginLeft: data.user === "me" ? "auto" : undefined, background: ownMessage ? "#0253B3" : "#F0F0F3", color: ownMessage ? "white" : "black", borderRadius: "1rem", padding: "1rem", }} > {data.text} </div> </div> ); }; function App() { const virtuoso = React.useRef<VirtuosoMessageListMethods<Message>>(null); return ( <div className="tall-example" style={{ height: 500, display: "flex", flexDirection: "column", fontSize: "70%", }} > <VirtuosoMessageListLicense licenseKey=""> <VirtuosoMessageList<Message, null> ref={virtuoso} style={{ flex: 1 }} computeItemKey={({ data }) => data.key} ItemContent={ItemContent} /> </VirtuosoMessageListLicense> <button onClick={() => { const myMessage = randomMessage("me"); virtuoso.current?.data.append( [myMessage], ({ scrollInProgress, atBottom }) => { return { index: "LAST", align: "start", behavior: atBottom || scrollInProgress ? "smooth" : "auto", }; }, ); setTimeout(() => { const botMessage = randomMessage("other"); virtuoso.current?.data.append([botMessage]); let counter = 0; const interval = setInterval(() => { if (counter++ > 20) { clearInterval(interval); } virtuoso.current?.data.map((message) => { return message.key === botMessage.key ? { ...message, text: message.text + " " + randPhrase() } : message; }, "smooth"); }, 150); }, 1000); }} > Ask the bot a question! </button> </div> ); } render(<App />);
Result
Loading...