feat: add home scroll top mask
This commit is contained in:
@@ -252,3 +252,35 @@
|
|||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-secondary);
|
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%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { View, Text, Image, Input, Button, Swiper, SwiperItem } from '@tarojs/components'
|
import { View, Text, Image, Input, Button, Swiper, SwiperItem } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import { CATEGORIES } from '../../utils/productConfig'
|
import { CATEGORIES } from '../../utils/productConfig'
|
||||||
import { assetUrl } from '../../utils/asset'
|
import { assetUrl } from '../../utils/asset'
|
||||||
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
|
|||||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||||
|
import { getTopMaskProgress } from '../../utils/homeTopMask'
|
||||||
|
|
||||||
// 成品展示配图(从 img 里取对应实物图)
|
// 成品展示配图(从 img 里取对应实物图)
|
||||||
const SHOWCASE_LIST = [
|
const SHOWCASE_LIST = [
|
||||||
@@ -70,6 +71,63 @@ export default function Index() {
|
|||||||
useStatusBar(resolvedTheme)
|
useStatusBar(resolvedTheme)
|
||||||
const [searchKey, setSearchKey] = useState('')
|
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) => {
|
const navigateToProduct = (categoryId: string) => {
|
||||||
Taro.navigateTo({ url: `/pages/product/index?id=${categoryId}` })
|
Taro.navigateTo({ url: `/pages/product/index?id=${categoryId}` })
|
||||||
}
|
}
|
||||||
@@ -98,6 +156,13 @@ export default function Index() {
|
|||||||
return (
|
return (
|
||||||
<View className={`theme-${resolvedTheme}`}>
|
<View className={`theme-${resolvedTheme}`}>
|
||||||
<ThemedPageMeta />
|
<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'>
|
<View className='index-page'>
|
||||||
|
|
||||||
{/* 页头 */}
|
{/* 页头 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user