feat: add home scroll top mask

This commit is contained in:
2026-08-08 01:38:36 +08:00
parent 8ac8558142
commit fc4b8f7e3d
2 changed files with 98 additions and 1 deletions
+32
View File
@@ -252,3 +252,35 @@
font-size: 24rpx;
color: var(--text-secondary);
}
.home-top-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 40;
pointer-events: none;
transform: translateZ(0);
will-change: opacity;
}
.theme-light .home-top-mask {
background: linear-gradient(
to bottom,
rgba(250, 247, 242, 0.98) 0%,
rgba(250, 247, 242, 0.92) 38%,
rgba(250, 247, 242, 0.48) 70%,
rgba(250, 247, 242, 0) 100%
);
}
.theme-dark .home-top-mask {
background: linear-gradient(
to bottom,
rgba(25, 25, 25, 0.98) 0%,
rgba(25, 25, 25, 0.92) 38%,
rgba(25, 25, 25, 0.48) 70%,
rgba(25, 25, 25, 0) 100%
);
}
+66 -1
View File
@@ -1,6 +1,6 @@
import { View, Text, Image, Input, Button, Swiper, SwiperItem } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import './index.scss'
import { CATEGORIES } from '../../utils/productConfig'
import { assetUrl } from '../../utils/asset'
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import { getTopMaskProgress } from '../../utils/homeTopMask'
// 成品展示配图(从 img 里取对应实物图)
const SHOWCASE_LIST = [
@@ -70,6 +71,63 @@ export default function Index() {
useStatusBar(resolvedTheme)
const [searchKey, setSearchKey] = useState('')
const [topMaskOpacity, setTopMaskOpacity] = useState(0)
const titleMetricsRef = useRef({ startY: 0, rangeY: 48 })
const scrollFrameRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const latestScrollTopRef = useRef(0)
const applyTopMaskProgress = (scrollTop: number) => {
latestScrollTopRef.current = scrollTop
if (scrollFrameRef.current !== null) {
return
}
scrollFrameRef.current = setTimeout(() => {
scrollFrameRef.current = null
const { startY, rangeY } = titleMetricsRef.current
setTopMaskOpacity(getTopMaskProgress({ scrollTop: latestScrollTopRef.current, startY, rangeY }))
}, 16)
}
useEffect(() => {
const measureTitle = () => {
Taro.createSelectorQuery()
.select('.home-header')
.boundingClientRect((rect: { top?: number; bottom?: number; height?: number } | null) => {
if (!rect || typeof rect.bottom !== 'number' || typeof rect.height !== 'number') {
const fallbackRange = Math.max(48, safe.headerPaddingTop * 0.45)
titleMetricsRef.current = {
startY: Math.max(0, safe.headerPaddingTop - safe.statusBarHeight),
rangeY: fallbackRange
}
applyTopMaskProgress(latestScrollTopRef.current)
return
}
const startY = Math.max(0, rect.bottom - safe.statusBarHeight - 8)
const rangeY = Math.max(32, rect.height * 0.7)
titleMetricsRef.current = { startY, rangeY }
applyTopMaskProgress(latestScrollTopRef.current)
})
.exec()
}
Taro.nextTick(() => {
setTimeout(measureTitle, 80)
})
return () => {
if (scrollFrameRef.current !== null) {
clearTimeout(scrollFrameRef.current)
}
scrollFrameRef.current = null
}
}, [safe.headerPaddingTop, safe.statusBarHeight])
Taro.usePageScroll((e) => {
applyTopMaskProgress(e.scrollTop)
})
const navigateToProduct = (categoryId: string) => {
Taro.navigateTo({ url: `/pages/product/index?id=${categoryId}` })
}
@@ -98,6 +156,13 @@ export default function Index() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<View
className='home-top-mask'
style={{
height: `${Math.max(safe.menuButtonTop + safe.menuButtonHeight + 18, safe.statusBarHeight + 52)}px`,
opacity: topMaskOpacity
}}
/>
<View className='index-page'>
{/* 页头 */}