Fix reader page scaling with applied safe area inset

The safe area inset wasn't applied everywhere, which potentially caused the scrollbar to appear while the pages should be fitting the screen

fixes #1102
This commit is contained in:
schroda
2026-05-12 13:26:40 +02:00
parent f2d60f69e7
commit 8dada86285
9 changed files with 55 additions and 19 deletions

View File

@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] (Preview)
### Fixed
- (**Reader**) Fix scrollbar appearing with "fit to widt/height/screen" page scale mode and applied safe area insets
## [20260509.01] (r3147) - 2026-05-09
### Fixed

View File

@@ -350,6 +350,7 @@ export interface ReaderPagerProps
| 'customFilter'
| 'shouldStretchPage'
| 'readerWidth'
| 'safeAreaInset'
>,
Pick<NavbarContextType, 'readerNavBarWidth'> {
onLoad?: (pagesIndex: number, url: string, isPrimary?: boolean) => void;

View File

@@ -17,6 +17,7 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick<NavbarContextType, 're
value: { red, green, blue, alpha, blendMode },
enabled,
} = useReaderSettingsStore((state) => state.customFilter.rgba);
const safeAreaInset = useReaderSettingsStore('safeAreaInset');
if (!enabled) {
return null;
@@ -26,10 +27,10 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick<NavbarContextType, 're
<Box
sx={{
position: 'fixed',
top: 0,
left: readerNavBarWidth,
right: 0,
bottom: 0,
top: safeAreaInset.top ? 'env(safe-area-inset-top)' : 0,
left: `calc(${readerNavBarWidth}px - ${safeAreaInset.left ? 'env(safe-area-inset-left)' : '0px'})`,
right: safeAreaInset.right ? 'env(safe-area-inset-right)' : 0,
bottom: safeAreaInset.bottom ? 'env(safe-area-inset-bottom)' : 0,
pointerEvents: 'none',
background: `rgba(${red} ${green} ${blue} / ${alpha}%)`,
mixBlendMode: `${blendMode}`,

View File

@@ -12,6 +12,7 @@ import type {
IReaderSettingsWithDefaultFlag,
ProgressBarPositionAutoVertical,
ReaderPagerProps,
SafeAreaInset,
} from '@/features/reader/Reader.types.ts';
import { ProgressBarPosition, ReaderPageScaleMode, ReadingMode } from '@/features/reader/Reader.types.ts';
import type { MangaGenreInfo, MangaSourceNameInfo } from '@/features/manga/Manga.types.ts';
@@ -20,6 +21,7 @@ import { ReaderPagedPager } from '@/features/reader/viewer/pager/components/Read
import { ReaderDoublePagedPager } from '@/features/reader/viewer/pager/components/ReaderDoublePagedPager.tsx';
import { ReaderVerticalPager } from '@/features/reader/viewer/pager/components/ReaderVerticalPager.tsx';
import { ReaderHorizontalPager } from '@/features/reader/viewer/pager/components/ReaderHorizontalPager.tsx';
import { ScrollDirection } from '@/base/Base.types.ts';
export const isOffsetDoubleSpreadPagesEditable = (readingMode: IReaderSettings['readingMode']): boolean =>
readingMode === ReadingMode.DOUBLE_PAGE;
@@ -96,3 +98,13 @@ export const getProgressBarPosition = (
return ProgressBarPosition.BOTTOM;
};
export const getSafeAreaInsets = (
safeAreaInset: SafeAreaInset,
direction: Exclude<ScrollDirection, ScrollDirection.XY>,
): string[] => [
...(direction === ScrollDirection.Y && safeAreaInset.top ? ['env(safe-area-inset-top)'] : []),
...(direction === ScrollDirection.X && safeAreaInset.right ? ['env(safe-area-inset-right)'] : []),
...(direction === ScrollDirection.Y && safeAreaInset.bottom ? ['env(safe-area-inset-bottom)'] : []),
...(direction === ScrollDirection.X && safeAreaInset.left ? ['env(safe-area-inset-left)'] : []),
];

View File

@@ -52,6 +52,7 @@ const BaseReaderChapterViewer = ({
retryFailedPagesKeyPrefix,
readingMode,
readerWidth,
safeAreaInset,
pageScaleMode,
shouldOffsetDoubleSpreads,
readingDirection,
@@ -427,6 +428,7 @@ const BaseReaderChapterViewer = ({
customFilter={customFilter}
shouldStretchPage={shouldStretchPage}
readerWidth={readerWidth}
safeAreaInset={safeAreaInset}
readerNavBarWidth={readerNavBarWidth}
isPreloadMode={isPreloadMode}
resumeMode={resumeMode}

View File

@@ -108,6 +108,7 @@ const BaseReaderViewer = ({
shouldStretchPage: state.shouldStretchPage.value,
isStaticNav: state.isStaticNav,
}));
const safeAreaInset = useReaderSettingsStore('safeAreaInset');
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
resumeMode: ReaderResumeMode.START,
};
@@ -337,6 +338,7 @@ const BaseReaderViewer = ({
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
readingMode={readingMode}
readerWidth={readerWidth}
safeAreaInset={safeAreaInset}
pageScaleMode={pageScaleMode}
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
readingDirection={readingDirection}

View File

@@ -19,6 +19,8 @@ import { applyStyles } from '@/base/utils/ApplyStyles.ts';
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
import { useReaderScrollbarStore } from '@/features/reader/stores/ReaderStore.ts';
import { ScrollDirection } from '@/base/Base.types.ts';
import { getSafeAreaInsets } from '@/features/reader/settings/ReaderSettings.utils.tsx';
const getCustomFilterString = (customFilter: ReaderCustomFilter): string =>
Object.keys(customFilter)
@@ -63,6 +65,7 @@ const BaseReaderPage = ({
pageScaleMode,
shouldStretchPage,
readerWidth,
safeAreaInset,
onLoad,
onError,
setRef,
@@ -70,7 +73,10 @@ const BaseReaderPage = ({
isLoaded,
...props
}: Omit<SpinnerImageProps, 'spinnerStyle' | 'imgStyle' | 'onLoad' | 'onError'> &
Pick<IReaderSettings, 'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth'> &
Pick<
IReaderSettings,
'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth' | 'safeAreaInset'
> &
Pick<NavbarContextType, 'readerNavBarWidth'> & {
pageIndex: number;
pagesIndex: number;
@@ -115,8 +121,8 @@ const BaseReaderPage = ({
shouldStretchPage,
pageScaleMode,
readerWidth,
readerNavBarWidth + scrollbar.ySize,
scrollbar.xSize,
[readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)],
[scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)],
doublePage,
isTabletWidth,
),
@@ -132,8 +138,8 @@ const BaseReaderPage = ({
pageScaleMode,
doublePage,
readerWidth,
readerNavBarWidth + scrollbar.ySize,
scrollbar.xSize,
[readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)],
[scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)],
),
filter: getCustomFilterString(customFilter),
objectFit: 'contain',

View File

@@ -56,25 +56,28 @@ const getPageWidthPercentage = (
return 1;
};
const joinOffsetsForCssCalc = (offsets: (number | string)[], operator: '-' | '+' = '-'): string =>
offsets.map((offset) => (typeof offset === 'number' ? `${offset}px` : offset)).join(` ${operator} `);
export const getImagePlaceholderStyling = (
readingMode: IReaderSettings['readingMode'],
shouldStretchPage: IReaderSettings['shouldStretchPage'],
pageScaleMode: IReaderSettings['pageScaleMode'],
readerWidth: IReaderSettings['readerWidth'],
widthOffset: number,
heightOffset: number,
widthOffset: (number | string)[],
heightOffset: (number | string)[],
isDoublePage?: boolean,
isTabletWidth?: boolean,
): CSSObject => {
const OVER_9000 = 9000;
const getMaxWidth = (width: string) => `calc(${width} - ${widthOffset}px)`;
const getMaxWidth = (width: string) => `calc(${width} - ${joinOffsetsForCssCalc(widthOffset)})`;
const getDesktopWidth = (width: number, readerWidthValue?: number) =>
getMaxWidth(`${coerceIn(readerWidthValue ?? width, Math.min(width, readerWidthValue ?? OVER_9000), width)}vw`);
const setReaderWidth = getSetReaderWidth(readerWidth, pageScaleMode);
const fullWidth = getMaxWidth(`${Math.min(100, setReaderWidth ?? OVER_9000)}vw`);
const fullHeight = `calc(100vh - ${heightOffset}px)`;
const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`;
const DEFAULT_SINGLE_PAGE_WIDTH = isTabletWidth ? fullWidth : getDesktopWidth(40, setReaderWidth);
const DEFAULT_SINGLE_PAGE_HEIGHT = isTabletWidth ? fullHeight : '85vh';
@@ -151,11 +154,11 @@ const getReaderDimensionStyling = (
readingMode: IReaderSettings['readingMode'],
shouldStretchPage: IReaderSettings['shouldStretchPage'],
pageScaleMode: IReaderSettings['pageScaleMode'],
widthOffset: number,
heightOffset: number,
widthOffset: (number | string)[],
heightOffset: (number | string)[],
): CSSObject => {
const fullWidth = `calc((100vw - ${widthOffset}px) * ${widthPercentage})`;
const fullHeight = `calc(100vh - ${heightOffset}px)`;
const fullWidth = `calc((100vw - ${joinOffsetsForCssCalc(widthOffset)}) * ${widthPercentage})`;
const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`;
switch (pageScaleMode) {
case ReaderPageScaleMode.WIDTH:
@@ -216,8 +219,8 @@ export const getReaderImageStyling = (
pageScaleMode: IReaderSettings['pageScaleMode'],
isDoublePage: boolean,
readerWidth: IReaderSettings['readerWidth'],
widthOffset: number,
heightOffset: number,
widthOffset: (number | string)[],
heightOffset: (number | string)[],
): CSSObject => {
const widthPercentage = getPageWidthPercentage(pageScaleMode, isDoublePage, readerWidth, true);
return getReaderDimensionStyling(
@@ -292,6 +295,7 @@ export const createReaderPage = (
pageScaleMode: IReaderSettings['pageScaleMode'],
shouldStretchPage: IReaderSettings['shouldStretchPage'],
readerWidth: IReaderSettings['readerWidth'],
safeAreaInset: IReaderSettings['safeAreaInset'],
readerNavBarWidth: NavbarContextType['readerNavBarWidth'],
retryKeyPrefix?: string,
position?: 'left' | 'right',
@@ -322,6 +326,7 @@ export const createReaderPage = (
pageScaleMode={pageScaleMode}
shouldStretchPage={shouldStretchPage}
readerWidth={readerWidth}
safeAreaInset={safeAreaInset}
readerNavBarWidth={readerNavBarWidth}
/>
);

View File

@@ -35,6 +35,7 @@ const BaseBasePager = ({
pageScaleMode,
shouldStretchPage,
readerWidth,
safeAreaInset,
readerNavBarWidth,
resumeMode,
handleAsInitialRender,
@@ -52,6 +53,7 @@ const BaseBasePager = ({
pageScaleMode: ReaderPagerProps['pageScaleMode'],
shouldStretchPage: ReaderPagerProps['shouldStretchPage'],
readerWidth: ReaderPagerProps['readerWidth'],
safeAreaInset: ReaderPagerProps['safeAreaInset'],
readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'],
) => ReactNode;
slots?: { boxProps?: BoxProps };
@@ -125,6 +127,7 @@ const BaseBasePager = ({
pageScaleMode,
shouldStretchPage,
readerWidth,
safeAreaInset,
readerNavBarWidth,
),
)}