Kaynağa Gözat

fix: keep home activity categories fixed while scrolling

Developer 5 gün önce
ebeveyn
işleme
eee634e447
2 değiştirilmiş dosya ile 55 ekleme ve 20 silme
  1. 51 18
      pages/home.vue
  2. 4 2
      tests/home-scroll-sticky-category.test.js

+ 51 - 18
pages/home.vue

@@ -8,7 +8,7 @@
 			</template>
 		</up-navbar>
 		<image src="https://oss.familydaf.cn/sxsnfile/20251218/ee40161fef4647f0b736d118e554dc2f.png" class="top_bg_img" mode="widthFix"></image>
-		<scroll-view class="c-box adffc" scroll-y="true" @scrolltolower="scrolltolower">
+		<scroll-view class="c-box adffc" scroll-y="true" @scroll="handleScroll" @scrolltolower="scrolltolower">
 				<view class="c-box-search">
 				<cus-search @handleSearch="toSearch"></cus-search>
 			</view>
@@ -37,7 +37,8 @@
 				<view class="c-box-title-left">公益活动</view>
 				<view class="c-box-title-right adfac" @click="toTurnPage('/pagesHome/allActivity',false)">更多 <up-icon name="arrow-right" size="30rpx" style="margin-left: 10rpx;"></up-icon></view>
 			</view>
-			<view class="c-box-typegrid" v-if="typeGrid.length">
+			<view class="c-box-typegrid-anchor" v-if="typeGrid.length" :style="isTypeGridSticky ? { height: typeGridHeight + 'px' } : null">
+			<view class="c-box-typegrid" :class="{ 'is-typegrid-sticky': isTypeGridSticky }" :style="isTypeGridSticky ? { top: mt + 'px' } : null">
 				<swiper class="typegrid-swiper" :current="gridIndex" @change="e => gridIndex = e.detail.current" :duration="200" :circular="false" :disable-touch="false">
 					<swiper-item v-for="(page, pi) in typeGridPages" :key="pi">
 						<view class="typegrid-page">
@@ -50,10 +51,11 @@
 						</view>
 					</swiper-item>
 				</swiper>
-				<view class="typegrid-dots adf" v-if="typeGridPages.length > 1">
-					<view class="typegrid-dot" v-for="(_, i) in typeGridPages" :key="i" :class="{'active': i === gridIndex}"></view>
-				</view>
-			</view>
+				<view class="typegrid-dots adf" v-if="typeGridPages.length > 1">
+					<view class="typegrid-dot" v-for="(_, i) in typeGridPages" :key="i" :class="{'active': i === gridIndex}"></view>
+				</view>
+			</view>
+			</view>
 			<view class="c-box-list" v-if="activityList.length">
 				<view class="activity-list-item" v-for="(item, index) in activityList" :key="item.id">
 						<NonprofitActivety :item="item" :index="index"></NonprofitActivety>
@@ -125,8 +127,11 @@
 	const current = ref(0)
 	const typeGrid = ref([])
 	const gridIndex = ref(0)
-	const isOver = ref(false)
-	const activityList = ref([])
+	const isOver = ref(false)
+	const activityList = ref([])
+	const isTypeGridSticky = ref(false)
+	const typeGridOffsetTop = ref(0)
+	const typeGridHeight = ref(0)
 	// 进入页面默认查全部(categoryId 为空),点图标网格才切到具体分类
 	const queryParams = ref({
 		page:1,
@@ -193,14 +198,36 @@
 		})
 	}
 	
-	const getTypeList = () => {
-		proxy.$api.get('/core/activity/category/list').then(({data:res})=>{
-			if(res.code!==0) return proxy.$showToast(res.msg)
+	const getTypeList = () => {
+		proxy.$api.get('/core/activity/category/list').then(({data:res})=>{
+			if(res.code!==0) return proxy.$showToast(res.msg)
 			// icon 网格:取所有配过 icon 的分类;超过 8 个会自动分页
-			typeGrid.value = res.data.filter(d=>d.iconUrl).map(d=>({id:d.id,name:d.categoryName,iconUrl:d.iconUrl}));
-			gridIndex.value = 0
-		})
-	}
+			typeGrid.value = res.data.filter(d=>d.iconUrl).map(d=>({id:d.id,name:d.categoryName,iconUrl:d.iconUrl}));
+			gridIndex.value = 0
+			measureTypeGrid()
+		})
+	}
+
+	const measureTypeGrid = () => {
+		nextTick(() => {
+			const query = uni.createSelectorQuery().in(proxy)
+			query.select('.c-box').boundingClientRect()
+			query.select('.c-box-typegrid-anchor').boundingClientRect()
+			query.exec((rects) => {
+				const boxRect = rects?.[0]
+				const gridRect = rects?.[1]
+				if (!boxRect || !gridRect) return
+				typeGridOffsetTop.value = gridRect.top - boxRect.top
+				typeGridHeight.value = gridRect.height
+			})
+		})
+	}
+
+	const handleScroll = (event) => {
+		if (!typeGridOffsetTop.value) return
+		const scrollTop = event?.detail?.scrollTop || 0
+		isTypeGridSticky.value = scrollTop >= typeGridOffsetTop.value
+	}
 	
 	const scrolltolower = () => {
 		if(isOver.value) return
@@ -348,13 +375,15 @@
 			}
 			&-typegrid{
 				width: 100%;
-				margin-top: 36rpx;
 				padding: 8rpx 0;
 				box-sizing: border-box;
-				position: sticky;
-				top: 0;
 				z-index: 10;
 				background: #FFFFFF;
+				&.is-typegrid-sticky{
+					position: fixed;
+					left: 0;
+					right: 0;
+				}
 				.typegrid-swiper{
 					width: 100%;
 					height: 360rpx;
@@ -410,6 +439,10 @@
 				width: 100%;
 				margin-top: 20rpx;
 			}
+			&-typegrid-anchor{
+				width: 100%;
+				margin-top: 36rpx;
+			}
 			.activity-list-item{
 				width: 100%;
 			}

+ 4 - 2
tests/home-scroll-sticky-category.test.js

@@ -9,8 +9,10 @@ test('首页使用单一滚动容器并让两行活动分类吸顶', () => {
   const source = fs.readFileSync(homeFile, 'utf8')
 
   assert.match(source, /const TYPEGRID_PAGE_SIZE\s*=\s*8/)
-  assert.match(source, /<scroll-view class="c-box adffc"[^>]*scroll-y[^>]*@scrolltolower="scrolltolower"/)
+  assert.match(source, /<scroll-view class="c-box adffc"[^>]*scroll-y[^>]*@scroll="handleScroll"[^>]*@scrolltolower="scrolltolower"/)
   assert.match(source, /<view class="typegrid-page[\s\S]*?flex-wrap:\s*wrap/)
-  assert.match(source, /&-typegrid\s*\{[\s\S]*?position:\s*sticky[\s\S]*?top:\s*0/)
+  assert.match(source, /isTypeGridSticky/)
+  assert.match(source, /\.is-typegrid-sticky\s*\{[\s\S]*?position:\s*fixed/)
+  assert.doesNotMatch(source, /position:\s*sticky/)
   assert.doesNotMatch(source, /<up-list[\s\S]*?@scrolltolower="scrolltolower"/)
 })