Rebuild useScroll, less states

This commit is contained in:
Lim Chee Aun 2023-12-29 18:29:08 +08:00
parent de3787209e
commit d7d838ebf8
3 changed files with 214 additions and 33 deletions

View file

@ -1092,6 +1092,13 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
transform: translate(-50%, 0); transform: translate(-50%, 0);
font-size: 90%; font-size: 90%;
pointer-events: auto; pointer-events: auto;
transition: all 0.3s ease-in-out;
header[hidden] & {
opacity: 0;
transform: translate(-50%, -100%) scale(0.9);
pointer-events: none;
}
} }
.updates-button .icon { .updates-button .icon {
vertical-align: top; vertical-align: top;

View file

@ -12,6 +12,7 @@ import { groupBoosts, groupContext } from '../utils/timeline-utils';
import useInterval from '../utils/useInterval'; import useInterval from '../utils/useInterval';
import usePageVisibility from '../utils/usePageVisibility'; import usePageVisibility from '../utils/usePageVisibility';
import useScroll from '../utils/useScroll'; import useScroll from '../utils/useScroll';
import useScrollFn from '../utils/useScrollFn';
import Icon from './icon'; import Icon from './icon';
import Link from './link'; import Link from './link';
@ -203,17 +204,47 @@ function Timeline({
} }
}); });
const { // const {
scrollDirection, // scrollDirection,
nearReachStart, // nearReachStart,
nearReachEnd, // nearReachEnd,
reachStart, // reachStart,
reachEnd, // reachEnd,
} = useScroll({ // } = useScroll({
scrollableRef, // scrollableRef,
distanceFromEnd: 2, // distanceFromEnd: 2,
scrollThresholdStart: 44, // scrollThresholdStart: 44,
}); // });
const headerRef = useRef();
// const [hiddenUI, setHiddenUI] = useState(false);
const [nearReachStart, setNearReachStart] = useState(false);
useScrollFn(
{
scrollableRef,
distanceFromEnd: 2,
scrollThresholdStart: 44,
},
({
scrollDirection,
nearReachStart,
nearReachEnd,
reachStart,
reachEnd,
}) => {
// setHiddenUI(scrollDirection === 'end' && !nearReachEnd);
if (headerRef.current) {
const hiddenUI = scrollDirection === 'end' && !nearReachStart;
headerRef.current.hidden = hiddenUI;
}
setNearReachStart(nearReachStart);
if (reachStart) {
loadItems(true);
} else if (nearReachEnd || (reachEnd && showMore)) {
loadItems();
}
},
[],
);
useEffect(() => { useEffect(() => {
scrollableRef.current?.scrollTo({ top: 0 }); scrollableRef.current?.scrollTo({ top: 0 });
@ -223,17 +254,17 @@ function Timeline({
loadItems(true); loadItems(true);
}, [refresh]); }, [refresh]);
useEffect(() => { // useEffect(() => {
if (reachStart) { // if (reachStart) {
loadItems(true); // loadItems(true);
} // }
}, [reachStart]); // }, [reachStart]);
useEffect(() => { // useEffect(() => {
if (nearReachEnd || (reachEnd && showMore)) { // if (nearReachEnd || (reachEnd && showMore)) {
loadItems(); // loadItems();
} // }
}, [nearReachEnd, showMore]); // }, [nearReachEnd, showMore]);
const prevView = useRef(view); const prevView = useRef(view);
useEffect(() => { useEffect(() => {
@ -304,7 +335,7 @@ function Timeline({
: null, : null,
); );
const hiddenUI = scrollDirection === 'end' && !nearReachStart; // const hiddenUI = scrollDirection === 'end' && !nearReachStart;
return ( return (
<FilterContext.Provider value={filterContext}> <FilterContext.Provider value={filterContext}>
@ -321,7 +352,8 @@ function Timeline({
> >
<div class="timeline-deck deck"> <div class="timeline-deck deck">
<header <header
hidden={hiddenUI} ref={headerRef}
// hidden={hiddenUI}
onClick={(e) => { onClick={(e) => {
if (!e.target.closest('a, button')) { if (!e.target.closest('a, button')) {
scrollableRef.current?.scrollTo({ scrollableRef.current?.scrollTo({
@ -356,7 +388,7 @@ function Timeline({
</div> </div>
{items.length > 0 && {items.length > 0 &&
uiState !== 'loading' && uiState !== 'loading' &&
!hiddenUI && // !hiddenUI &&
showNew && ( showNew && (
<button <button
class="updates-button shiny-pill" class="updates-button shiny-pill"
@ -657,13 +689,27 @@ function TimelineItem({
function StatusCarousel({ title, class: className, children }) { function StatusCarousel({ title, class: className, children }) {
const carouselRef = useRef(); const carouselRef = useRef();
const { reachStart, reachEnd, init } = useScroll({ // const { reachStart, reachEnd, init } = useScroll({
scrollableRef: carouselRef, // scrollableRef: carouselRef,
direction: 'horizontal', // direction: 'horizontal',
}); // });
useEffect(() => { const startButtonRef = useRef();
init?.(); const endButtonRef = useRef();
}, []); useScrollFn(
{
scrollableRef: carouselRef,
direction: 'horizontal',
init: true,
},
({ reachStart, reachEnd }) => {
if (startButtonRef.current) startButtonRef.current.disabled = reachStart;
if (endButtonRef.current) endButtonRef.current.disabled = reachEnd;
},
[],
);
// useEffect(() => {
// init?.();
// }, []);
return ( return (
<div class={`status-carousel ${className}`}> <div class={`status-carousel ${className}`}>
@ -671,9 +717,10 @@ function StatusCarousel({ title, class: className, children }) {
<h3>{title}</h3> <h3>{title}</h3>
<span> <span>
<button <button
ref={startButtonRef}
type="button" type="button"
class="small plain2" class="small plain2"
disabled={reachStart} // disabled={reachStart}
onClick={() => { onClick={() => {
carouselRef.current?.scrollBy({ carouselRef.current?.scrollBy({
left: -Math.min(320, carouselRef.current?.offsetWidth), left: -Math.min(320, carouselRef.current?.offsetWidth),
@ -684,9 +731,10 @@ function StatusCarousel({ title, class: className, children }) {
<Icon icon="chevron-left" /> <Icon icon="chevron-left" />
</button>{' '} </button>{' '}
<button <button
ref={endButtonRef}
type="button" type="button"
class="small plain2" class="small plain2"
disabled={reachEnd} // disabled={reachEnd}
onClick={() => { onClick={() => {
carouselRef.current?.scrollBy({ carouselRef.current?.scrollBy({
left: Math.min(320, carouselRef.current?.offsetWidth), left: Math.min(320, carouselRef.current?.offsetWidth),

126
src/utils/useScrollFn.js Normal file
View file

@ -0,0 +1,126 @@
import { useEffect, useLayoutEffect, useState } from 'preact/hooks';
export default function useScrollFn(
{
scrollableRef,
distanceFromStart = 1, // ratio of clientHeight/clientWidth
distanceFromEnd = 1, // ratio of clientHeight/clientWidth
scrollThresholdStart = 10,
scrollThresholdEnd = 10,
direction = 'vertical',
distanceFromStartPx: _distanceFromStartPx,
distanceFromEndPx: _distanceFromEndPx,
init,
} = {},
callback,
deps,
) {
if (!callback) return;
const [scrollDirection, setScrollDirection] = useState(null);
const [reachStart, setReachStart] = useState(false);
const [reachEnd, setReachEnd] = useState(false);
const [nearReachStart, setNearReachStart] = useState(false);
const [nearReachEnd, setNearReachEnd] = useState(false);
const isVertical = direction === 'vertical';
useLayoutEffect(() => {
const scrollableElement = scrollableRef.current;
if (!scrollableElement) return {};
let previousScrollStart = isVertical
? scrollableElement.scrollTop
: scrollableElement.scrollLeft;
function onScroll() {
const {
scrollTop,
scrollLeft,
scrollHeight,
scrollWidth,
clientHeight,
clientWidth,
} = scrollableElement;
const scrollStart = isVertical ? scrollTop : scrollLeft;
const scrollDimension = isVertical ? scrollHeight : scrollWidth;
const clientDimension = isVertical ? clientHeight : clientWidth;
const scrollDistance = Math.abs(scrollStart - previousScrollStart);
const distanceFromStartPx =
_distanceFromStartPx ||
Math.min(
clientDimension * distanceFromStart,
scrollDimension,
scrollStart,
);
const distanceFromEndPx =
_distanceFromEndPx ||
Math.min(
clientDimension * distanceFromEnd,
scrollDimension,
scrollDimension - scrollStart - clientDimension,
);
if (
scrollDistance >=
(previousScrollStart < scrollStart
? scrollThresholdEnd
: scrollThresholdStart)
) {
setScrollDirection(previousScrollStart < scrollStart ? 'end' : 'start');
previousScrollStart = scrollStart;
}
setReachStart(scrollStart <= 0);
setReachEnd(scrollStart + clientDimension >= scrollDimension);
setNearReachStart(scrollStart <= distanceFromStartPx);
setNearReachEnd(
scrollStart + clientDimension >= scrollDimension - distanceFromEndPx,
);
}
scrollableElement.addEventListener('scroll', onScroll, { passive: true });
return () => scrollableElement.removeEventListener('scroll', onScroll);
}, [
distanceFromStart,
distanceFromEnd,
scrollThresholdStart,
scrollThresholdEnd,
]);
useEffect(() => {
callback({
scrollDirection,
reachStart,
reachEnd,
nearReachStart,
nearReachEnd,
});
}, [
scrollDirection,
reachStart,
reachEnd,
nearReachStart,
nearReachEnd,
...deps,
]);
useEffect(() => {
if (init && scrollableRef.current) {
queueMicrotask(() => {
scrollableRef.current.dispatchEvent(new Event('scroll'));
});
}
}, [init]);
// return {
// scrollDirection,
// reachStart,
// reachEnd,
// nearReachStart,
// nearReachEnd,
// init: () => {
// if (scrollableRef.current) {
// scrollableRef.current.dispatchEvent(new Event('scroll'));
// }
// },
// };
}