refactor: scroll to bottom while new msg coming in
This commit is contained in:
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user