Skip to content

Commit 72b553d

Browse files
committed
feat: add generateEscapeScrollUpContext, for disbale auto scroll when user scroll up.
BREAKING CHANGE: 1. remove escapeHook, use context.escapeHook instead. 2. iife usage update: autoScroll.default, autoScroll.generateEscapeScrollUpContext
1 parent a8b71ba commit 72b553d

File tree

4 files changed

+175
-28
lines changed

4 files changed

+175
-28
lines changed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ import autoScroll from "@yrobot/auto-scroll";
2828
autoScroll({ selector: "#scroll-container-id" });
2929
```
3030

31-
## script
31+
## script - iife
3232

3333
```html
3434
<script src="https://cdn.jsdelivr.net/npm/@yrobot/auto-scroll/build/index.iife.js"></script>
3535
<script>
36-
autoScroll({ selector: "#scroll-container-id" });
36+
autoScroll.default({ selector: "#scroll-container-id" });
3737
</script>
3838
```
3939

@@ -43,3 +43,27 @@ autoScroll({ selector: "#scroll-container-id" });
4343

4444
- [] The subtree children list length increase
4545
- [] The direct child element height increase
46+
47+
## Pack Up Useful Utilities Logic
48+
49+
### Stop auto scroll when user scroll up
50+
51+
> es
52+
53+
```ts
54+
import autoScroll, { generateEscapeScrollUpContext } from "@yrobot/auto-scroll";
55+
56+
autoScroll({
57+
selector: "#scroll-container-id",
58+
context: generateEscapeScrollUpContext(),
59+
});
60+
```
61+
62+
> iife
63+
64+
```ts
65+
autoScroll.default({
66+
selector: "#scroll-container-id",
67+
context: autoScroll.generateEscapeScrollUpContext(),
68+
});
69+
```

package/index.ts

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,83 @@ const throttle = <R, A extends any[]>(
2525
];
2626
};
2727

28+
type OnUnmount = (elm: Element) => void;
29+
30+
export type Context = {
31+
escapeHook?: (elm: Element) => boolean;
32+
onMount?: (elm: Element) => void | OnUnmount;
33+
onUnmount?: OnUnmount;
34+
};
35+
36+
/**
37+
* Generates the context for escaping auto scroll down when user scroll up.
38+
*
39+
* @param {Object} config - The configs.
40+
* @param {number} [config.threshold=24] - The threshold value for scroll up distance (default: 24).
41+
* @param {number} [config.throttleTime=100] - The throttle time for scroll event (default: 100).
42+
*
43+
* @returns {Context} The generated context object. For autoScroll.param.context
44+
*/
45+
export function generateEscapeScrollUpContext({
46+
threshold = 24,
47+
throttleTime = 100,
48+
}: { threshold?: number; throttleTime?: number } = {}) {
49+
const context: Context = {};
50+
let isEscape = false;
51+
const [onScroll] = throttle((evt: Event) => {
52+
const target = evt.target as Element;
53+
const scrollUpDistance =
54+
target.scrollHeight - target.scrollTop - target.clientHeight;
55+
isEscape = scrollUpDistance > threshold;
56+
}, throttleTime);
57+
context.onMount = (elm) => {
58+
elm.addEventListener("scroll", onScroll);
59+
};
60+
context.onUnmount = (elm) => {
61+
elm.removeEventListener("scroll", onScroll);
62+
};
63+
context.escapeHook = () => isEscape;
64+
return context;
65+
}
66+
2867
/**
2968
* @description auto scroll the selector dom to the bottom, when the size of the selector dom has been updated.
30-
* @author Yrobot <https://yrobot.top>
31-
* @date 12/01/2024
3269
*
3370
* @param {Object} options - The config options for the autoScroll function.
34-
* @param {string} [options.selector] - The selector for the container element. (example: '#container')
35-
* @param {EscapeHook} [options.escapeHook] - A function that determines whether scrolling should be escaped.
36-
* @param {number} [options.throttleTime] - The throttle time in milliseconds.
37-
* @param {number} [options.offset] - The offset for the scroll position based on the container.scrollHeight.
71+
* @param {string} options.selector - The selector for the container element. (example: '#container')
72+
* @param {Context} [options.context] - The context for the life cycle hooks of the autoScroll function. [escapeHook,onMount,onUnmount]
73+
* @param {number} [options.throttleTime=100] - The throttle time in milliseconds.
74+
* @param {number} [options.offset=0] - The offset for the scroll position based on the container.scrollHeight.
3875
*
3976
* @return {function} The unObserverCallback function.
77+
*
78+
* @example autoScroll({ selector: "#scroll-container-id" })
79+
* @example autoScroll({ selector: "#scroll-container-id", context: generateEscapeScrollUpContext() })
4080
*/
41-
function autoScroll({
81+
export default function autoScroll({
4282
selector,
43-
escapeHook = (elm) => false,
4483
throttleTime = 100,
84+
context,
4585
offset = 0,
4686
}: {
4787
selector: string;
48-
escapeHook?: (elm: Element) => boolean;
4988
throttleTime?: number;
89+
context?: Context;
5090
offset?: number;
5191
}): unObserverCallback {
5292
const container = document.querySelector(selector);
5393

5494
if (container === null)
5595
throw new Error(`Element not found with selector [${selector}]`);
5696

97+
const returnOnUnmount = context?.onMount?.(container);
98+
5799
const [scrollHook] = throttle(() => {
58-
if (escapeHook(container)) return false;
100+
if (
101+
typeof context?.escapeHook === "function" &&
102+
context.escapeHook(container)
103+
)
104+
return false;
59105
container.scrollTop = container.scrollHeight + offset;
60106
return true;
61107
}, throttleTime);
@@ -81,7 +127,8 @@ function autoScroll({
81127
return () => {
82128
resizeObserver.disconnect();
83129
mutationObserver.disconnect();
130+
// unmount
131+
returnOnUnmount?.(container);
132+
context?.onUnmount?.(container);
84133
};
85134
}
86-
87-
export default autoScroll;

website/index.css

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ body {
77
}
88
.panel {
99
width: 400px;
10-
padding: 24px;
1110
}
1211
.list-container {
12+
box-sizing: border-box;
1313
border: 1px solid #000;
1414
padding: 8px;
1515
width: 100%;
16-
height: 800px;
16+
height: 600px;
1717
overflow: scroll;
1818
display: flex;
1919
flex-direction: column;
@@ -40,3 +40,13 @@ body {
4040
background-color: black;
4141
color: #fff;
4242
}
43+
44+
.grid-table {
45+
display: grid;
46+
grid-template-columns: 1fr 1fr;
47+
gap: 64px;
48+
}
49+
50+
code {
51+
white-space: pre-wrap;
52+
}

website/index.tsx

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,116 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect, useRef, useState } from "react";
22
import { createRoot } from "react-dom/client";
33

4-
import autoScroll from "../package/index.ts";
4+
import autoScroll, { generateEscapeScrollUpContext } from "../package/index.ts";
55

66
import "./index.css";
77

8-
function App() {
8+
const useDynamicList = ({ max = 999 }: { max?: number } = {}) => {
99
const [list, setList] = useState<number[]>([]);
10+
const dataRef = useRef({
11+
len: 0,
12+
max,
13+
});
14+
dataRef.current.len = list.length;
15+
dataRef.current.max = max;
1016
useEffect(() => {
1117
const timeId = setInterval(() => {
12-
if (list.length > 999) {
13-
setList((list) => list.slice(0, 10));
14-
} else {
18+
if (dataRef.current.len < dataRef.current.max) {
1519
setList((list) => [...list, list.length + 1]);
1620
}
1721
}, 200);
1822
return () => {
1923
clearInterval(timeId);
2024
};
2125
}, []);
22-
useEffect(() => autoScroll({ selector: "#list-container" }), []);
26+
return list;
27+
};
28+
29+
const codes = {
30+
default: `
31+
import autoScroll from "@yrobot/auto-scroll";
32+
33+
autoScroll({ selector: "#scroll-container-id" });`,
34+
escapeScrollUp: `
35+
import autoScroll, { generateEscapeScrollUpContext } from "@yrobot/auto-scroll";
36+
37+
autoScroll({
38+
selector: "#scroll-container-id",
39+
context: generateEscapeScrollUpContext(),
40+
});`,
41+
};
42+
43+
const DefaultDemo = () => {
44+
const list = useDynamicList();
45+
useEffect(
46+
() =>
47+
autoScroll({
48+
selector: "#default-list-container",
49+
}),
50+
[]
51+
);
2352
return (
2453
<div className="panel">
25-
<div className="list-container" id="list-container">
26-
{/* {list.map((id) => (
54+
<h3>Default (auto scroll always)</h3>
55+
<div className="list-container" id="default-list-container">
56+
{list.map((id) => (
2757
<div className="item" key={id}>
2858
{id}
2959
</div>
30-
))} */}
60+
))}
3161
{/* <div>
3262
{list.map((id) => (
3363
<div className="item" key={id}>
3464
{id}
3565
</div>
3666
))}
3767
</div> */}
38-
<div
68+
{/* <div
3969
className="item"
4070
style={{
4171
height: list.length * 100,
4272
}}
4373
>
4474
Height Update [{list.length * 100}px]
45-
</div>
75+
</div> */}
4676
<div className="loading">LOADING...</div>
4777
</div>
78+
<code>{codes.default}</code>
79+
</div>
80+
);
81+
};
82+
83+
const EscapeScrollUpDemo = () => {
84+
const list = useDynamicList();
85+
useEffect(
86+
() =>
87+
autoScroll({
88+
selector: "#escape-scroll-up-list-container",
89+
context: generateEscapeScrollUpContext(),
90+
}),
91+
[]
92+
);
93+
return (
94+
<div className="panel">
95+
<h3>Stop Auto Scroll When User Scroll Up</h3>
96+
<div className="list-container" id="escape-scroll-up-list-container">
97+
{list.map((id) => (
98+
<div className="item" key={id}>
99+
{id}
100+
</div>
101+
))}
102+
<div className="loading">LOADING...</div>
103+
</div>
104+
<code>{codes.escapeScrollUp}</code>
105+
</div>
106+
);
107+
};
108+
109+
function App() {
110+
return (
111+
<div className="grid-table">
112+
<DefaultDemo />
113+
<EscapeScrollUpDemo />
48114
</div>
49115
);
50116
}

0 commit comments

Comments
 (0)