Skip to content

Commit

Permalink
fix: prevent overscroll bounce in some scene
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Nov 22, 2024
1 parent e30783b commit 11803c8
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 96 deletions.
3 changes: 2 additions & 1 deletion apps/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"fake-indexeddb": "6.0.0",
"happy-dom": "15.8.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-scan": "0.0.16"
}
}
3 changes: 2 additions & 1 deletion apps/renderer/src/components/common/PoweredByFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import pkg from "@pkg"

export const PoweredByFooter: Component = ({ className }) => (
<footer className={cn("center mt-12 flex gap-2", className)}>
Powered by <Logo className="size-5" />{" "}
{new Date().getFullYear()}
<Logo className="size-5" />{" "}
<a
href={pkg.homepage}
className="cursor-pointer font-bold text-accent no-underline"
Expand Down
2 changes: 2 additions & 0 deletions apps/renderer/src/hooks/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./useBizQuery"
export * from "./useContextMenu"
export * from "./useI18n"
export * from "./usePreventOverscrollBounce"
export * from "./useSwitchHotkeyScope"
export * from "./useSyncTheme"
37 changes: 37 additions & 0 deletions apps/renderer/src/hooks/common/usePreventOverscrollBounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useEffect } from "react"

const PREVENT_SPRING_CLASS = "prevent-spring"

/**
* Prevent overscroll bounce
* @param enabled - Whether to enable the prevention of overscroll bounce, default is true
*/
export const usePreventOverscrollBounce = (enabled = true) => {
useEffect(() => {
if (!enabled) return

// If has style element, skip
if (document.querySelector(`#${PREVENT_SPRING_CLASS}`)) {
return
}

const styleElement = document.createElement("style")
styleElement.id = PREVENT_SPRING_CLASS
styleElement.textContent = `
[data-${PREVENT_SPRING_CLASS}] {
overscroll-behavior: none !important;
}
`

document.head.append(styleElement)

document.documentElement.dataset.preventSpring = "true"
document.body.dataset.preventSpring = "true"

return () => {
delete document.documentElement.dataset.preventSpring
delete document.body.dataset.preventSpring
styleElement.remove()
}
}, [enabled])
}
2 changes: 2 additions & 0 deletions apps/renderer/src/modules/app-layout/entry-column/mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useGeneralSettingKey } from "~/atoms/settings/general"
import { ROUTE_FEED_PENDING } from "~/constants/app"
import { ENTRY_COLUMN_LIST_SCROLLER_ID, LOGO_MOBILE_ID } from "~/constants/dom"
import { navigateEntry } from "~/hooks/biz/useNavigateEntry"
import { usePreventOverscrollBounce } from "~/hooks/common"
import { EntryColumn } from "~/modules/entry-column"

import { MobileFloatBar } from "../feed-column/float-bar.mobile"
Expand All @@ -20,6 +21,7 @@ export const EntryColumnMobile = () => {
}, 1000)
return () => clearTimeout(timer)
}, [])
usePreventOverscrollBounce()
return (
<div className="flex h-screen min-w-0 grow">
<EntryColumn />
Expand Down
4 changes: 1 addition & 3 deletions apps/renderer/src/modules/entry-content/header.mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ const HeaderRightActions = ({
return () => clearTimeout(timeout)
}, [getSetMarkdownElement])

const { currentScrollRange, handleScrollTo } = useScrollTracking(toc, {
useWindowScroll: true,
})
const { currentScrollRange, handleScrollTo } = useScrollTracking(toc, {})

return (
<div className={clsx(className, "flex items-center gap-2 text-zinc-500")}>
Expand Down
191 changes: 100 additions & 91 deletions apps/renderer/src/modules/entry-content/index.mobile.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx"
import { ScrollElementContext } from "@follow/components/ui/scroll-area/ctx.js"
import { useTitle } from "@follow/hooks"
import type { FeedModel, InboxModel } from "@follow/models/types"
import { stopPropagation } from "@follow/utils/dom"
import { cn } from "@follow/utils/utils"
import { ErrorBoundary } from "@sentry/react"
import { useMemo } from "react"
import { useMemo, useState } from "react"
import { useTranslation } from "react-i18next"

import { useAudioPlayerAtomSelector } from "~/atoms/player"
import { useUISettingKey } from "~/atoms/settings/ui"
import { ShadowDOM } from "~/components/common/ShadowDOM"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useAuthQuery } from "~/hooks/common"
import { useAuthQuery, usePreventOverscrollBounce } from "~/hooks/common"
import { LanguageMap } from "~/lib/translate"
import { WrappedElementProvider } from "~/providers/wrapped-element-provider"
import { Queries } from "~/queries"
Expand Down Expand Up @@ -92,6 +93,8 @@ export const EntryContent: Component<{

const { entryId: audioEntryId } = useAudioPlayerAtomSelector((state) => state)

usePreventOverscrollBounce()
const [scrollElement, setScrollElement] = useState<HTMLElement | null>(null)
if (!entry) return null

const content = entry?.entries.content ?? data?.entries.content
Expand Down Expand Up @@ -124,105 +127,111 @@ export const EntryContent: Component<{

return (
<WrappedElementProvider>
<EntryHeader
entryId={entry.entries.id}
view={view}
className={cn(
"sticky top-0 z-[12] h-[55px] shrink-0 bg-background px-3 @container",
classNames?.header,
)}
compact={compact}
/>

<div className="relative mt-12 flex min-w-0 flex-col px-4 @container print:size-auto print:overflow-visible">
{!hideRecentReader && (
<div
<ScrollElementContext.Provider value={scrollElement}>
<div className="flex h-screen flex-col">
<EntryHeader
entryId={entry.entries.id}
view={view}
className={cn(
"absolute top-0 my-2 -mt-8 flex items-center gap-2 text-[13px] leading-none text-zinc-500",
"visible z-[11]",
"sticky top-0 z-[12] h-[55px] shrink-0 bg-background px-3 @container",
classNames?.header,
)}
compact={compact}
/>
<div
className="relative flex h-0 min-w-0 grow flex-col overflow-auto px-4 pt-12 @container print:!size-auto print:!overflow-visible"
ref={setScrollElement}
>
<EntryReadHistory entryId={entryId} />
</div>
)}

<div
className="duration-200 ease-in-out animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:fade-in-0 f-motion-reduce:slide-in-from-bottom-0"
key={entry.entries.id}
>
<article
onContextMenu={stopPropagation}
className="relative m-auto min-w-0 max-w-[550px]"
>
<EntryTitle entryId={entryId} compact={compact} />

{audioEntryId === entryId && (
<CornerPlayer className="mx-auto !mt-4 w-full overflow-hidden rounded-md md:w-[350px]" />
{!hideRecentReader && (
<div
className={cn(
"absolute top-0 my-2 -mt-8 flex items-center gap-2 text-[13px] leading-none text-zinc-500",
"visible z-[11]",
)}
>
<EntryReadHistory entryId={entryId} />
</div>
)}

<WrappedElementProvider boundingDetection>
<div className="mx-auto mb-32 mt-8 max-w-full cursor-auto select-text text-[0.94rem]">
<TitleMetaHandler entryId={entry.entries.id} />
{(summary.isLoading || summary.data) && (
<div className="my-8 space-y-1 rounded-lg border px-4 py-3">
<div className="flex items-center gap-2 font-medium text-zinc-800 dark:text-neutral-400">
<i className="i-mgc-magic-2-cute-re align-middle" />
<span>{t("entry_content.ai_summary")}</span>
</div>
<AutoResizeHeight spring className="text-sm leading-relaxed">
{summary.isLoading ? SummaryLoadingSkeleton : summary.data}
</AutoResizeHeight>
</div>
<div
className="duration-200 ease-in-out animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:fade-in-0 f-motion-reduce:slide-in-from-bottom-0"
key={entry.entries.id}
>
<article
onContextMenu={stopPropagation}
className="relative m-auto min-w-0 max-w-[550px]"
>
<EntryTitle entryId={entryId} compact={compact} />

{audioEntryId === entryId && (
<CornerPlayer className="mx-auto !mt-4 w-full overflow-hidden rounded-md md:w-[350px]" />
)}
<ErrorBoundary fallback={RenderError}>
<ShadowDOM injectHostStyles={!isInbox}>
<EntryContentHTMLRenderer
view={view}
feedId={feed?.id}
entryId={entryId}
handleTranslate={translate}
mediaInfo={mediaInfo}
noMedia={noMedia}
as="article"
className="prose !max-w-full dark:prose-invert prose-h1:text-[1.6em] prose-h1:font-bold"
renderInlineStyle={readerRenderInlineStyle}
>
{content}
</EntryContentHTMLRenderer>
</ShadowDOM>
</ErrorBoundary>
</div>
</WrappedElementProvider>

{!content && (
<div className="center mt-16 min-w-0">
{isPending ? (
<EntryContentLoading
icon={!isInbox ? (feed as FeedModel)?.siteUrl! : undefined}
/>
) : error ? (
<div className="center flex min-w-0 flex-col gap-2">
<i className="i-mgc-close-cute-re text-3xl text-red-500" />
<span className="font-sans text-sm">Network Error</span>

<pre className="mt-6 w-full overflow-auto whitespace-pre-wrap break-all">
{error.message}
</pre>

<WrappedElementProvider boundingDetection>
<div className="mx-auto mb-32 mt-8 max-w-full cursor-auto select-text text-[0.94rem]">
<TitleMetaHandler entryId={entry.entries.id} />
{(summary.isLoading || summary.data) && (
<div className="my-8 space-y-1 rounded-lg border px-4 py-3">
<div className="flex items-center gap-2 font-medium text-zinc-800 dark:text-neutral-400">
<i className="i-mgc-magic-2-cute-re align-middle" />
<span>{t("entry_content.ai_summary")}</span>
</div>
<AutoResizeHeight spring className="text-sm leading-relaxed">
{summary.isLoading ? SummaryLoadingSkeleton : summary.data}
</AutoResizeHeight>
</div>
)}
<ErrorBoundary fallback={RenderError}>
<ShadowDOM injectHostStyles={!isInbox}>
<EntryContentHTMLRenderer
view={view}
feedId={feed?.id}
entryId={entryId}
handleTranslate={translate}
mediaInfo={mediaInfo}
noMedia={noMedia}
as="article"
className="prose !max-w-full dark:prose-invert prose-h1:text-[1.6em] prose-h1:font-bold"
renderInlineStyle={readerRenderInlineStyle}
>
{content}
</EntryContentHTMLRenderer>
</ShadowDOM>
</ErrorBoundary>
</div>
</WrappedElementProvider>

{!content && (
<div className="center mt-16 min-w-0">
{isPending ? (
<EntryContentLoading
icon={!isInbox ? (feed as FeedModel)?.siteUrl! : undefined}
/>
) : error ? (
<div className="center flex min-w-0 flex-col gap-2">
<i className="i-mgc-close-cute-re text-3xl text-red-500" />
<span className="font-sans text-sm">Network Error</span>

<pre className="mt-6 w-full overflow-auto whitespace-pre-wrap break-all">
{error.message}
</pre>
</div>
) : (
<NoContent
id={entry.entries.id}
url={entry.entries.url ?? ""}
sourceContent={entry.settings?.sourceContent}
/>
)}
</div>
) : (
<NoContent
id={entry.entries.id}
url={entry.entries.url ?? ""}
sourceContent={entry.settings?.sourceContent}
/>
)}
</div>
)}

<SupportCreator entryId={entryId} />
</article>
<SupportCreator entryId={entryId} />
</article>
</div>
</div>
</div>
</div>
</ScrollElementContext.Provider>
</WrappedElementProvider>
)
}
2 changes: 2 additions & 0 deletions apps/renderer/src/wdyr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ if (import.meta.env.DEV) {
whyDidYouRender.default(React as any, {
trackAllPureComponents: true,
})
const { scan } = await import("react-scan")
scan({ enabled: true, log: true, showToolbar: true })
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 11803c8

Please sign in to comment.