Skip to content

Commit 3ab2272

Browse files
committed
feat: ✨ 实现自动滚动功能
#249
1 parent a414a90 commit 3ab2272

15 files changed

Lines changed: 204 additions & 7 deletions

File tree

locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"server_connect_failed": "Unable to connect to the server"
1111
},
1212
"button": {
13+
"auto_scroll": "Auto scroll",
1314
"close_current_page_translation": "Close translation of the current page",
1415
"download_completed": "Download completed",
1516
"download_completed_error": "Download complete, but {{errorNum}} images failed to download",
@@ -71,6 +72,7 @@
7172
"other": {
7273
"auto": "Auto",
7374
"disable": "Disable",
75+
"distance": "distance",
7476
"download": "Download",
7577
"enabled": "Enabled",
7678
"enter_comic_read_mode": "Enter comic reading mode",
@@ -80,6 +82,7 @@
8082
"fill_page": "Fill Page",
8183
"hotkeys": "Hotkeys",
8284
"img_loading": "Image loading",
85+
"interval": "interval",
8386
"loading_img": "Loading image",
8487
"none": "None",
8588
"or": "or",
@@ -128,6 +131,7 @@
128131
"always_load_all_img": "Always load all images",
129132
"autoFullscreen": "Auto fullscreen",
130133
"autoHiddenMouse": "Auto hide mouse",
134+
"auto_scroll_trigger_end": "Continue scrolling on the end page",
131135
"auto_switch_page_mode": "Auto switch single/double page mode by aspect ratio",
132136
"background_color": "Background Color",
133137
"click_page_turn_area": "Touch area",

locales/hu.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"server_connect_failed": "Nem sikerült csatlakozni a szerverhez."
1111
},
1212
"button": {
13+
"auto_scroll": "Automatikus görgetés",
1314
"close_current_page_translation": "A jelenlegi oldal fordításának bezárása.",
1415
"download_completed": "Letöltés befejezve",
1516
"download_completed_error": "Letöltés befejezve, de {{errorNum}} kép letöltése sikertelen",
@@ -71,6 +72,7 @@
7172
"other": {
7273
"auto": "自动",
7374
"disable": "Letiltás",
75+
"distance": "távolság",
7476
"download": "Letöltés",
7577
"enabled": "Engedélyezés",
7678
"enter_comic_read_mode": "Belépés a manga olvasó módba",
@@ -80,6 +82,7 @@
8082
"fill_page": "填充页",
8183
"hotkeys": "快捷键",
8284
"img_loading": "图片加载中",
85+
"interval": "intervallum",
8386
"loading_img": "加载图片中",
8487
"none": "",
8588
"or": "",
@@ -128,6 +131,7 @@
128131
"always_load_all_img": "始终加载所有图片",
129132
"autoFullscreen": "Automatikus teljes képernyő",
130133
"autoHiddenMouse": "自动隐藏鼠标",
134+
"auto_scroll_trigger_end": "Görgetés folytatása a végoldalon",
131135
"auto_switch_page_mode": "根据屏幕比例切换单双页",
132136
"background_color": "背景颜色",
133137
"click_page_turn_area": "点击区域",

locales/ru.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"server_connect_failed": "Не удалось подключиться к серверу"
1111
},
1212
"button": {
13+
"auto_scroll": "Автопрокрутка",
1314
"close_current_page_translation": "Скрыть перевод текущей страницы",
1415
"download_completed": "Загрузка завершена",
1516
"download_completed_error": "Загрузка завершена, но {{errorNum}} изображений не удалось загрузить",
@@ -71,6 +72,7 @@
7172
"other": {
7273
"auto": "Авто",
7374
"disable": "Отключить",
75+
"distance": "расстояние",
7476
"download": "Скачать",
7577
"enabled": "Включено",
7678
"enter_comic_read_mode": "Режим чтения комиксов",
@@ -80,6 +82,7 @@
8082
"fill_page": "Заполнить страницу",
8183
"hotkeys": "Горячие клавиши",
8284
"img_loading": "Изображение загружается",
85+
"interval": "интервал",
8386
"loading_img": "Загрузка изображения",
8487
"none": "Отсутствует",
8588
"or": "или",
@@ -128,6 +131,7 @@
128131
"always_load_all_img": "Всегда загружать все изображения",
129132
"autoFullscreen": "Авто полный экран",
130133
"autoHiddenMouse": "Автоматически скрывать курсор мыши",
134+
"auto_scroll_trigger_end": "Продолжить прокрутку на конечной странице",
131135
"auto_switch_page_mode": "Автоматическое переключение режима одной/двойной страницы в зависимости от соотношения сторон",
132136
"background_color": "Цвет фона",
133137
"click_page_turn_area": "Область нажатия",

locales/ta.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"server_connect_failed": "சேவையகத்துடன் இணைக்க முடியாது"
1111
},
1212
"button": {
13+
"auto_scroll": "தானியங்கி உருட்டல்",
1314
"close_current_page_translation": "தற்போதைய பக்கத்தின் மொழிபெயர்ப்பை அணைக்கவும்",
1415
"download_completed": "பதிவிறக்குங்கள்",
1516
"download_completed_error": "பதிவிறக்கம் முடிந்தது, ஆனால் {{errorNum}} படங்களை பதிவிறக்க முடியவில்லை",
@@ -71,6 +72,7 @@
7172
"other": {
7273
"auto": "தானியங்கி",
7374
"disable": "முடக்கவும்",
75+
"distance": "தூரம்",
7476
"download": "பதிவிறக்குங்கள்",
7577
"enabled": "திறந்திருக்கும்",
7678
"enter_comic_read_mode": "காமிக் வாசிப்பு பயன்முறையை உள்ளிடவும்",
@@ -80,6 +82,7 @@
8082
"fill_page": "பக்கங்கள்",
8183
"hotkeys": "குறுக்குவழி விசை",
8284
"img_loading": "படம் ஏற்றுகிறது",
85+
"interval": "இடைவெளி",
8386
"loading_img": "படத்தை ஏற்றவும்",
8487
"none": "இல்லை",
8588
"or": "அல்லது",
@@ -128,6 +131,7 @@
128131
"always_load_all_img": "எப்போதும் எல்லா படங்களையும் ஏற்றவும்",
129132
"autoFullscreen": "தானியங்கி முழுத்திரை",
130133
"autoHiddenMouse": "தானியங்கி மறைக்கும் சுட்டி",
134+
"auto_scroll_trigger_end": "இறுதிப் பக்கத்தில் உருட்டலைத் தொடரவும்",
131135
"auto_switch_page_mode": "விகிதத்தின் அடிப்படையில் ஒற்றை/இரட்டை பக்க பயன்முறையை தானாக மாற்றவும்",
132136
"background_color": "பின்னணி நிறம்",
133137
"click_page_turn_area": "பகுதியைக் சொடுக்கு செய்க",

locales/zh.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"server_connect_failed": "无法连接到服务器"
1111
},
1212
"button": {
13+
"auto_scroll": "自动滚动",
1314
"close_current_page_translation": "关闭当前页的翻译",
1415
"download_completed": "下载完成",
1516
"download_completed_error": "下载完成,但有 {{errorNum}} 张图片下载失败",
@@ -71,6 +72,7 @@
7172
"other": {
7273
"auto": "自动",
7374
"disable": "禁用",
75+
"distance": "距离",
7476
"download": "下载",
7577
"enabled": "启用",
7678
"enter_comic_read_mode": "进入漫画阅读模式",
@@ -80,6 +82,7 @@
8082
"fill_page": "填充页",
8183
"hotkeys": "快捷键",
8284
"img_loading": "图片加载中",
85+
"interval": "间隔",
8386
"loading_img": "加载图片中",
8487
"none": "",
8588
"or": "",
@@ -128,6 +131,7 @@
128131
"always_load_all_img": "始终加载所有图片",
129132
"autoFullscreen": "自动全屏",
130133
"autoHiddenMouse": "自动隐藏鼠标",
134+
"auto_scroll_trigger_end": "在结束页上继续滚动",
131135
"auto_switch_page_mode": "根据屏幕比例切换单双页",
132136
"background_color": "背景颜色",
133137
"click_page_turn_area": "点击区域",

src/components/IconButton/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface IconButtonProps {
2222
popperClassName?: string | boolean;
2323
children?: JSX.Element;
2424
onClick?: EventHandler['on:click'];
25+
style?: JSX.CSSProperties;
2526
}
2627

2728
/** 图标按钮 */
@@ -46,6 +47,7 @@ export const IconButton: Component<IconButtonProps> = (_props) => {
4647
aria-label={props.tip}
4748
type="button"
4849
class={classes.iconButton}
50+
style={props.style}
4951
classList={{
5052
[classes.hidden]: props.hidden,
5153
[classes.enabled]: props.enabled,

src/components/Manga/actions/hotkeys.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export const [defaultHotkeys, setDefaultHotkeys] = createSignal<
2424
translate_current_page: [],
2525
translate_all: [],
2626
translate_to_end: [],
27-
fullscreen: [''],
27+
fullscreen: [],
28+
auto_scroll: [],
2829
});
2930

3031
/** 快捷键配置 */

src/components/Manga/actions/operate.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
switchDir,
1717
switchGridMode,
1818
switchFullscreen,
19+
switchAutoScroll,
1920
} from './switch';
2021
import { handleTrackpadWheel } from './pointer';
2122
import { setOption } from './helper';
@@ -48,7 +49,7 @@ export const handleMouseDown: EventHandler['on:mousedown'] = (e) => {
4849
};
4950

5051
/** 卷轴模式下的页面滚动 */
51-
const scrollModeScrollPage = (x: number) => {
52+
export const scrollModeScrollPage = (x: number) => {
5253
if (!store.show.endPage) {
5354
scrollTo(scrollTop() + x, true);
5455
_setState('scrollLock', true);
@@ -159,11 +160,9 @@ export const handleKeyDown = (e: KeyboardEvent) => {
159160
if (isAbreastMode()) {
160161
switch (hotkey) {
161162
case 'scroll_up':
162-
setAbreastScrollFill(abreastScrollFill() - 20);
163-
return;
163+
return setAbreastScrollFill(abreastScrollFill() - 20);
164164
case 'scroll_down':
165-
setAbreastScrollFill(abreastScrollFill() + 20);
166-
return;
165+
return setAbreastScrollFill(abreastScrollFill() + 20);
167166

168167
case 'scroll_left':
169168
return scrollTo(
@@ -243,6 +242,8 @@ export const handleKeyDown = (e: KeyboardEvent) => {
243242
case 'translate_to_end':
244243
return translateToEnd();
245244

245+
case 'auto_scroll':
246+
return switchAutoScroll();
246247
case 'fullscreen':
247248
return switchFullscreen();
248249

src/components/Manga/actions/switch.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { refs, setState, store } from '../store';
1+
import { _setState, refs, setState, store } from '../store';
22

33
import { zoom } from './zoom';
44
import { setOption } from './helper';
@@ -91,3 +91,7 @@ export const switchFullscreen = () => {
9191
if (document.fullscreenElement) document.exitFullscreen();
9292
else refs.root.requestFullscreen();
9393
};
94+
95+
/** 切换自动滚动 */
96+
export const switchAutoScroll = () =>
97+
_setState('autoScroll', 'play', (val) => !val);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import MdPlayArrow from '@material-design-icons/svg/round/play_arrow.svg';
2+
import MdStop from '@material-design-icons/svg/round/stop.svg';
3+
import { AnimationFrame, createEffectOn, t } from 'helper';
4+
import { createMemo } from 'solid-js';
5+
6+
import { _setState, store } from '../store';
7+
import {
8+
isAbreastMode,
9+
isBottom,
10+
isScrollMode,
11+
scrollProgress,
12+
scrollTo,
13+
scrollTop,
14+
switchAutoScroll,
15+
turnPage,
16+
} from '../actions';
17+
import { IconButton } from '../../IconButton';
18+
19+
const autoScroll = new (class extends AnimationFrame {
20+
/** 上次滚动的时间 */
21+
lastTime = 0;
22+
23+
scroll = () => {
24+
if (store.show.endPage === 'end') {
25+
this.stop();
26+
if (store.option.autoScroll.triggerEnd) turnPage('next');
27+
return;
28+
}
29+
30+
if (!store.option.scrollMode.enabled) turnPage('next');
31+
else if (isScrollMode())
32+
scrollTo(scrollTop() + store.option.autoScroll.distance, true);
33+
else if (isAbreastMode())
34+
scrollTo(
35+
scrollProgress() -
36+
(store.option.dir === 'rtl' ? -1 : 1) *
37+
store.option.autoScroll.distance,
38+
true,
39+
);
40+
41+
if (!isBottom()) return;
42+
if (!store.prop.onExit) return this.stop();
43+
return _setState('show', 'endPage', 'end');
44+
};
45+
46+
frame = (timestamp: DOMHighResTimeStamp) => {
47+
const elapsed = timestamp - this.lastTime;
48+
let progress: number;
49+
if (elapsed >= store.option.autoScroll.interval) {
50+
this.lastTime = timestamp;
51+
this.scroll();
52+
progress = 1;
53+
}
54+
if (!store.autoScroll.play) return;
55+
56+
progress ||= elapsed / store.option.autoScroll.interval;
57+
_setState('autoScroll', 'progress', progress);
58+
this.call();
59+
};
60+
61+
start = () => {
62+
this.lastTime = 0;
63+
this.call();
64+
};
65+
66+
stop = () => {
67+
this.cancel();
68+
_setState('autoScroll', 'play', false);
69+
};
70+
})();
71+
72+
createEffectOn(
73+
() => [...Object.values(store.option.autoScroll), store.autoScroll.play],
74+
() => {
75+
autoScroll.cancel();
76+
if (!store.option.autoScroll.enabled || !store.autoScroll.play) return;
77+
autoScroll.start();
78+
},
79+
);
80+
81+
// 点击屏幕中间停止自动滚动
82+
createEffectOn(
83+
() => store.show.toolbar,
84+
(show) => show && autoScroll.stop(),
85+
);
86+
87+
export const AutoScrollButton = () => {
88+
const background = createMemo(() => {
89+
if (!store.autoScroll.play) return undefined;
90+
const deg = (store.autoScroll.progress * 360) % 360;
91+
return `conic-gradient(var(--text-secondary) 0deg, var(--text-secondary) ${deg}deg, var(--text) ${deg}deg)`;
92+
});
93+
94+
return (
95+
<IconButton
96+
tip={t('button.auto_scroll')}
97+
enabled={store.autoScroll.play}
98+
style={{ background: background() }}
99+
onClick={switchAutoScroll}
100+
children={store.autoScroll.play ? <MdStop /> : <MdPlayArrow />}
101+
/>
102+
);
103+
};

0 commit comments

Comments
 (0)