refactor: scroll to bottom while new msg coming in

This commit is contained in:
Tristan Yang
2023-03-07 22:45:16 +08:00
parent 69595bec3c
commit d9343ec795
5 changed files with 59 additions and 25 deletions
@@ -1,6 +1,6 @@
import { useState, useEffect, FC } from "react"; import { useState, useEffect, FC } from "react";
import { Ping } from '@uiball/loaders'; import { Ping } from '@uiball/loaders';
// import { getDefaultSize } from "../../utils"; import { getDefaultSize, isMobile } from "../../utils";
type Props = { type Props = {
uploading: boolean; uploading: boolean;
@@ -20,7 +20,7 @@ const ImageMessage: FC<Props> = ({
properties properties
}) => { }) => {
const [url, setUrl] = useState(thumbnail); const [url, setUrl] = useState(thumbnail);
// const { width = 0, height = 0 } = getDefaultSize(properties); const { width = 0, height = 0 } = getDefaultSize(properties, { min: 200, max: isMobile() ? 300 : 480 });
useEffect(() => { useEffect(() => {
const newUrl = thumbnail; const newUrl = thumbnail;
const img = new Image(); const img = new Image();
@@ -34,7 +34,11 @@ const ImageMessage: FC<Props> = ({
}, [thumbnail]); }, [thumbnail]);
return ( return (
<div className="relative w-fit h-fit"> <div className={`relative`} style={{
width: width ? `${width}px` : "",
height: height ? `${height}px` : ""
}}
>
{uploading && ( {uploading && (
<div className="absolute left-0 top-0 w-full h-full bg-white/50 flex flex-col justify-center items-center gap-1"> <div className="absolute left-0 top-0 w-full h-full bg-white/50 flex flex-col justify-center items-center gap-1">
<Ping <Ping
@@ -46,7 +50,7 @@ const ImageMessage: FC<Props> = ({
</div> </div>
)} )}
<img <img
className="w-full h-auto max-w-[480px] cursor-zoom-in object-cover preview" className="h-auto w-full cursor-zoom-in object-cover preview"
// style={{ // style={{
// width: width ? `${width}px` : "", // width: width ? `${width}px` : "",
// height: height ? `${height}px` : "" // height: height ? `${height}px` : ""
@@ -56,6 +56,7 @@ const MemberList: FC<Props> = ({ cid }) => {
const uids = channel ? (channel.is_public ? users.ids : channel.members) : users.ids; const uids = channel ? (channel.is_public ? users.ids : channel.members) : users.ids;
return <ul className="flex flex-col gap-1 w-full md:w-[512px] mb-44" ref={ref}> return <ul className="flex flex-col gap-1 w-full md:w-[512px] mb-44" ref={ref}>
<ViewportList <ViewportList
initialPrerender={15}
viewportRef={ref} viewportRef={ref}
items={uids} items={uids}
> >
+5 -4
View File
@@ -50,21 +50,22 @@ export const isElementVisible = (el: Element | null) => {
|| el.contains(efp(rect.left, rect.bottom)) || el.contains(efp(rect.left, rect.bottom))
); );
}; };
export function getDefaultSize(size?: { width: number; height: number }, min = 480) { export function getDefaultSize(size?: { width: number; height: number }, limit?: { min: number; max: number }) {
if (!size) return { width: 0, height: 0 }; if (!size) return { width: 0, height: 0 };
const { min, max } = limit ?? { min: 200, max: 320 };
const { width: oWidth, height: oHeight } = size; const { width: oWidth, height: oHeight } = size;
if (oWidth == oHeight) { if (oWidth == oHeight) {
const tmp = min > oWidth ? oWidth : min; const tmp = min > oWidth ? min : (oWidth < max ? oWidth : max);
return { width: tmp, height: tmp }; return { width: tmp, height: tmp };
} }
const isVertical = oWidth <= oHeight; const isVertical = oWidth <= oHeight;
let dWidth = 0; let dWidth = 0;
let dHeight = 0; let dHeight = 0;
if (isVertical) { if (isVertical) {
dHeight = oHeight >= min ? min : oHeight; dHeight = oHeight < min ? min : (oHeight < max ? oHeight : max);
dWidth = (oWidth / oHeight) * dHeight; dWidth = (oWidth / oHeight) * dHeight;
} else { } else {
dWidth = oWidth >= min ? min : oWidth; dWidth = oWidth < min ? min : (oWidth < max ? oWidth : max);
dHeight = (oHeight / oWidth) * dWidth; dHeight = (oHeight / oWidth) * dWidth;
} }
return { width: dWidth, height: dHeight }; return { width: dWidth, height: dHeight };
+43 -14
View File
@@ -1,8 +1,8 @@
import { useEffect, useRef } from 'react'; import { memo, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { NavLink, useLocation } from 'react-router-dom'; import { NavLink, useLocation } from 'react-router-dom';
import { useDebounce } from 'rooks'; import { useDebounce } from 'rooks';
import { ViewportList } from 'react-viewport-list'; import { ViewportList, ViewportListRef } from 'react-viewport-list';
import { useReadMessageMutation } from '../../../app/services/message'; import { useReadMessageMutation } from '../../../app/services/message';
import { useAppSelector } from '../../../app/store'; import { useAppSelector } from '../../../app/store';
@@ -14,9 +14,9 @@ type Props = {
context: "user" | "channel", context: "user" | "channel",
id: number id: number
} }
const triggerScrollHeight = 400;
const MessageFeed = ({ context, id }: Props) => { const MessageFeed = ({ context, id }: Props) => {
// const listRef = useRef<ViewportListRef | null>(null); const listRef = useRef<ViewportListRef | null>(null);
const ref = useRef<HTMLDivElement | null>( const ref = useRef<HTMLDivElement | null>(
null, null,
); );
@@ -41,17 +41,45 @@ const MessageFeed = ({ context, id }: Props) => {
messageData: store.message || {} messageData: store.message || {}
}; };
}); });
// useEffect(() => { useEffect(() => {
// // 滚动到记忆位置 // 上下文有变动,则滚动到底部
// if (listRef && listRef.current) { console.log("listRef", listRef);
// listRef.current.scrollToIndex({ index: 5 }); if (listRef && listRef.current) {
// } const list = listRef.current;
// }, [context, id]); ref.current?.classList.remove("scroll-smooth");
list.scrollToIndex({
index: Number.POSITIVE_INFINITY
});
setTimeout(() => {
// tricky
ref.current?.classList.add("scroll-smooth");
}, 150);
}
}, [context, id]);
useEffect(() => {
// 需要检测滚动位置,如果距离底部超过阈值,则不滚到底部
if (ref && ref.current) {
const container = ref.current;
const { scrollHeight, scrollTop, offsetHeight } = container;
const deltaNum = scrollHeight - scrollTop - offsetHeight;
console.log("delta", deltaNum);
if (deltaNum < triggerScrollHeight) {
// scroll to bottom
container.scrollTop = container.scrollHeight;
}
}
}, [mids]);
const handleMessageListChange = (data: [number, number]) => {
const [first, last] = data;
console.log("index changed", data);
};
const readIndex = context == "channel" ? footprint.readChannels[id] : Number.POSITIVE_INFINITY; const readIndex = context == "channel" ? footprint.readChannels[id] : Number.POSITIVE_INFINITY;
const isEmptyList = !mids || mids.length == 0; const isEmptyList = !mids || mids.length == 0;
return ( return (
<article ref={ref} id={`VOCECHAT_FEED_${context}_${id}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll will-change-contents"> <article ref={ref} id={`VOCECHAT_FEED_${context}_${id}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll scroll-smooth will-change-contents">
{context == "channel" && <div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2"> {context == "channel" && <div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
<h2 className="font-bold text-4xl dark:text-white">{t("welcome_channel", { name: data?.name })}</h2> <h2 className="font-bold text-4xl dark:text-white">{t("welcome_channel", { name: data?.name })}</h2>
<p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p> <p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p>
@@ -63,11 +91,12 @@ const MessageFeed = ({ context, id }: Props) => {
)} )}
</div>} </div>}
{isEmptyList ? null : <ViewportList {isEmptyList ? null : <ViewportList
onViewportIndexesChange={handleMessageListChange}
overscan={10} overscan={10}
// itemSize={100} // itemSize={100}
initialPrerender={50} initialPrerender={30}
scrollThreshold={2000} scrollThreshold={2000}
// ref={listRef} ref={listRef}
viewportRef={ref} viewportRef={ref}
items={mids} items={mids}
> >
@@ -92,4 +121,4 @@ const MessageFeed = ({ context, id }: Props) => {
); );
}; };
export default MessageFeed; export default memo(MessageFeed);
+2 -3
View File
@@ -1,4 +1,4 @@
import { FC, useRef } from "react"; import { FC, memo, useRef } from "react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { ViewportList } from "react-viewport-list"; import { ViewportList } from "react-viewport-list";
import Session from "./Session"; import Session from "./Session";
@@ -81,7 +81,6 @@ const SessionList: FC<Props> = ({ tempSession }) => {
items={sessions} items={sessions}
> >
{(s) => { {(s) => {
console.log("ssss", sessions);
const { type, id, mid = 0 } = s; const { type, id, mid = 0 } = s;
const key = `${type}_${id}`; const key = `${type}_${id}`;
return ( return (
@@ -117,4 +116,4 @@ const SessionList: FC<Props> = ({ tempSession }) => {
</> </>
); );
}; };
export default SessionList; export default memo(SessionList);