Virtuoso Message Custom Smooth Scrolling
Custom Smooth Scrolling
Section titled “Custom Smooth Scrolling”In addition to the default 'smooth' and 'auto' scroll behavior values, the component accepts a custom function that accepts the current scroll location, the target scroll location and returns a custom payload that describes a smooth scroll.
import { VirtuosoMessageList, VirtuosoMessageListProps, VirtuosoMessageListMethods, VirtuosoMessageListLicense } from '@virtuoso.dev/message-list'
import {useRef} from 'react'
import {randTextRange, randPhrase} from '@ngneat/falso'
/**
* Bounce easing function - https://easings.net/#easeOutBounce. This is just an example, you can use any easing function.
*/
function easeOutBounce(x: number): number {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
}
/**
* If the location is too far, you can return a different smooth scroll behavior
*/
function customSmoothScroll(currentTop: number, targetTop: number) {
return {
// increase the animation frame count to smoothen and slow down the scroll.
animationFrameCount: 50,
easing: easeOutBounce,
};
}
export default function App() {
const virtuoso = useRef<VirtuosoMessageListMethods<Message>>(null);
return (
<div
className="wide-example full-code"
style={{
height: 500,
display: "flex",
flexDirection: "column",
fontSize: "70%",
}}
>
<VirtuosoMessageListLicense licenseKey="">
<VirtuosoMessageList<Message, null>
initialData={Array.from({ length: 100 }, (_, index) =>
randomMessage(index % 2 === 0 ? "me" : "other"),
)}
ref={virtuoso}
style={{ flex: 1 }}
computeItemKey={({ data }) => data.key}
initialLocation={{ index: "LAST", align: "end" }}
ItemContent={ItemContent}
/>
</VirtuosoMessageListLicense>
<button
style={{ marginTop: "1rem", fontSize: "1.1rem", padding: "1rem" }}
onClick={(e) => {
e.target.disabled = true;
const myMessage = randomMessage("me");
virtuoso.current?.data.append(
[myMessage],
({ scrollInProgress, atBottom }) => {
return {
index: "LAST",
align: "start",
behavior:
atBottom || scrollInProgress ? customSmoothScroll : "auto",
};
},
);
setTimeout(() => {
const botMessage = randomMessage("other");
virtuoso.current?.data.append([botMessage]);
let counter = 0;
const interval = setInterval(() => {
if (counter++ > 20) {
clearInterval(interval);
e.target.disabled = false;
}
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>
);
}
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: "60%",
marginLeft: data.user === "me" ? "auto" : undefined,
background: ownMessage
? "var(--background)"
: "var(--alt-background)",
border: '1px solid var(--border)',
borderRadius: "1rem",
padding: "1rem",
}}
>
{data.text}
</div>
</div>
);
};
;