mobile: swipe up to toggle header/footer (#205)

This commit is contained in:
QP Hou
2025-01-20 22:17:36 -08:00
committed by GitHub
parent a853bc52d2
commit 7fcaab8b9f
4 changed files with 123 additions and 5 deletions
@@ -4,7 +4,7 @@ import { BookConfig } from '@/types/book';
import { FoliateView, wrappedFoliateView } from '@/types/view';
import { useReaderStore } from '@/store/readerStore';
import { useParallelViewStore } from '@/store/parallelViewStore';
import { useClickEvent } from '../hooks/useClickEvent';
import { useClickEvent, useTouchEvent } from '../hooks/useClickEvent';
import { useFoliateEvents } from '../hooks/useFoliateEvents';
import { useProgressSync } from '../hooks/useProgressSync';
import { useAutoHideScrollbar } from '../hooks/useAutoHideScrollbar';
@@ -17,6 +17,9 @@ import {
handleMouseup,
handleClick,
handleWheel,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
} from '../utils/iframeEventHandlers';
const FoliateViewer: React.FC<{
@@ -42,7 +45,6 @@ const FoliateViewer: React.FC<{
const progressRelocateHandler = (event: Event) => {
const detail = (event as CustomEvent).detail;
// console.log('relocate:', detail);
setProgress(bookKey, detail.cfi, detail.tocItem, detail.section, detail.location, detail.range);
};
@@ -68,6 +70,9 @@ const FoliateViewer: React.FC<{
detail.doc.addEventListener('mouseup', handleMouseup.bind(null, bookKey));
detail.doc.addEventListener('click', handleClick.bind(null, bookKey));
detail.doc.addEventListener('wheel', handleWheel.bind(null, bookKey));
detail.doc.addEventListener('touchstart', handleTouchStart.bind(null, bookKey));
detail.doc.addEventListener('touchmove', handleTouchMove.bind(null, bookKey));
detail.doc.addEventListener('touchend', handleTouchEnd.bind(null, bookKey));
}
}
};
@@ -88,6 +93,7 @@ const FoliateViewer: React.FC<{
}
};
useTouchEvent(bookKey, viewRef);
const { handleTurnPage } = useClickEvent(bookKey, viewRef, containerRef);
useFoliateEvents(viewRef.current, {
onLoad: docLoadHandler,
@@ -31,7 +31,7 @@ export const useClickEvent = (
const centerEndX = rect.left + rect.width * 0.625;
if (screenX >= centerStartX && screenX <= centerEndX) {
// toggle visibility of the header bar and the footer bar
setHoveredBookKey(hoveredBookKey ? '' : bookKey);
setHoveredBookKey(hoveredBookKey ? null : bookKey);
} else if (screenX >= rect.left + rect.width / 2) {
viewRef.current?.goRight();
} else if (screenX < rect.left + rect.width / 2) {
@@ -73,3 +73,82 @@ export const useClickEvent = (
handleTurnPage,
};
};
interface IframeTouch {
clientX: number;
clientY: number;
screenX: number;
screenY: number;
}
interface IframeTouchEvent {
targetTouches: IframeTouch[];
}
export const useTouchEvent = (
bookKey: string,
viewRef: React.MutableRefObject<FoliateView | null>,
) => {
const { hoveredBookKey, setHoveredBookKey } = useReaderStore();
let touchStart: IframeTouch | null = null;
let touchEnd: IframeTouch | null = null;
const onTouchStart = (e: IframeTouchEvent) => {
touchEnd = null;
const touch = e.targetTouches[0];
if (!touch) return;
touchStart = touch;
};
const onTouchMove = (e: IframeTouchEvent) => {
if (!touchStart) return;
const touch = e.targetTouches[0];
if (touch) {
touchEnd = touch;
}
};
const onTouchEnd = (e: IframeTouchEvent) => {
if (!touchStart) return;
const touch = e.targetTouches[0];
if (touch) {
touchEnd = touch;
};
const windowWidth = window.innerWidth;
if (touchEnd) {
const deltaY = touchEnd.screenY - touchStart.screenY;
const deltaX = touchEnd.screenX - touchStart.screenX;
// also check for deltaX to prevent swipe page turn from triggering the toggle
if (deltaY < -10 && Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaX) < windowWidth * 0.3) {
// swipe up to toggle the header bar and the footer bar
setHoveredBookKey(hoveredBookKey ? null : bookKey);
}
}
touchStart = null;
touchEnd = null;
};
const handleTouch = (msg: MessageEvent) => {
if (msg.data && msg.data.bookKey === bookKey) {
if (msg.data.type === 'iframe-touchstart') {
onTouchStart(msg.data);
} else if (msg.data.type === 'iframe-touchmove') {
onTouchMove(msg.data);
} else if (msg.data.type === 'iframe-touchend') {
onTouchEnd(msg.data);
}
}
};
useEffect(() => {
window.addEventListener('message', handleTouch);
return () => {
window.removeEventListener('message', handleTouch);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hoveredBookKey, viewRef]);
}
@@ -130,3 +130,36 @@ export const handleClick = (bookKey: string, event: MouseEvent) => {
}
}, doubleClickThreshold);
};
const handleTouchEv = (bookKey: string, event: TouchEvent, type: string) => {
const touch = event.targetTouches[0];
const touches = [];
if (touch) {
touches.push({
clientX: touch.clientX,
clientY: touch.clientY,
screenX: touch.screenX,
screenY: touch.screenY,
});
}
window.postMessage(
{
type: type,
bookKey,
targetTouches: touches,
},
'*',
);
}
export const handleTouchStart = (bookKey: string, event: TouchEvent) => {
handleTouchEv(bookKey, event, 'iframe-touchstart');
};
export const handleTouchMove = (bookKey: string, event: TouchEvent) => {
handleTouchEv(bookKey, event, 'iframe-touchmove');
};
export const handleTouchEnd = (bookKey: string, event: TouchEvent) => {
handleTouchEv(bookKey, event, 'iframe-touchend');
};
+2 -2
View File
@@ -30,7 +30,7 @@ interface ReaderStore {
bookKeys: string[];
hoveredBookKey: string | null;
setBookKeys: (keys: string[]) => void;
setHoveredBookKey: (key: string) => void;
setHoveredBookKey: (key: string | null) => void;
setBookmarkRibbonVisibility: (key: string, visible: boolean) => void;
setProgress: (
@@ -59,7 +59,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
bookKeys: [],
hoveredBookKey: null,
setBookKeys: (keys: string[]) => set({ bookKeys: keys }),
setHoveredBookKey: (key: string) => set({ hoveredBookKey: key }),
setHoveredBookKey: (key: string | null) => set({ hoveredBookKey: key }),
getView: (key: string | null) => (key && get().viewStates[key]?.view) || null,
setView: (key: string, view) =>