在 UniApp 中,组件监听父页面滚动事件有多种实现方式,以下是详细的解决方案:
方法一:通过 Props 传递滚动位置(推荐)
父页面
<template>
<view class="container">
<!-- 监听页面滚动 -->
<view @scroll="handleScroll" scroll-y style="height: 100vh;">
<!-- 页面内容 -->
<view style="height: 2000px;">
<text>滚动内容...</text>
</view>
<!-- 子组件 -->
<my-component :scroll-top="scrollTop" />
</view>
</view>
</template>
<script>
export default {
data() {
return {
scrollTop: 0
}
},
methods: {
handleScroll(e) {
this.scrollTop = e.detail.scrollTop
console.log('滚动位置:', this.scrollTop)
}
}
}
</script>
子组件
<template>
<view class="sticky-component" :style="{ opacity: calculateOpacity }">
<text>我是监听滚动的组件</text>
</view>
</template>
<script>
export default {
props: {
scrollTop: {
type: Number,
default: 0
}
},
computed: {
calculateOpacity() {
// 根据滚动位置计算透明度
return Math.max(0, 1 - this.scrollTop / 500)
}
},
watch: {
scrollTop(newVal) {
console.log('组件监听到滚动:', newVal)
this.handleScroll(newVal)
}
},
methods: {
handleScroll(scrollTop) {
// 处理滚动逻辑
if (scrollTop > 100) {
this.showHeader = true
} else {
this.showHeader = false
}
}
}
}
</script>
方法二:使用全局事件总线
创建事件总线
// utils/eventBus.js
class EventBus {
constructor() {
this.events = {}
}
$on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
}
$emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data))
}
}
$off(event, callback) {
if (this.events[event]) {
if (callback) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
} else {
delete this.events[event]
}
}
}
}
export default new EventBus()
父页面
<template>
<view class="container" @scroll="handleScroll" scroll-y>
<!-- 页面内容 -->
<my-component />
</view>
</template>
<script>
import eventBus from '@/utils/eventBus.js'
export default {
methods: {
handleScroll(e) {
// 发布滚动事件
eventBus.$emit('pageScroll', {
scrollTop: e.detail.scrollTop,
scrollLeft: e.detail.scrollLeft
})
}
}
}
</script>
子组件
<template>
<view class="component">
<text>监听全局滚动的组件</text>
</view>
</template>
<script>
import eventBus from '@/utils/eventBus.js'
export default {
mounted() {
// 监听滚动事件
eventBus.$on('pageScroll', this.handleScroll)
},
beforeDestroy() {
// 组件销毁时移除监听
eventBus.$off('pageScroll', this.handleScroll)
},
methods: {
handleScroll(scrollData) {
console.log('收到滚动事件:', scrollData)
// 处理滚动逻辑
}
}
}
</script>
方法三:使用 Vuex 状态管理
Store 配置
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
scrollInfo: {
scrollTop: 0,
scrollLeft: 0
}
},
mutations: {
UPDATE_SCROLL_INFO(state, scrollInfo) {
state.scrollInfo = scrollInfo
}
},
actions: {
updateScrollInfo({ commit }, scrollInfo) {
commit('UPDATE_SCROLL_INFO', scrollInfo)
}
}
})
父页面
<template>
<view @scroll="handleScroll" scroll-y>
<my-component />
</view>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions(['updateScrollInfo']),
handleScroll(e) {
this.updateScrollInfo({
scrollTop: e.detail.scrollTop,
scrollLeft: e.detail.scrollLeft
})
}
}
}
</script>
子组件
<template>
<view class="component">
<text>当前滚动位置: {{ scrollTop }}px</text>
</view>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['scrollInfo']),
scrollTop() {
return this.scrollInfo.scrollTop
}
},
watch: {
scrollTop(newVal) {
console.log('滚动位置变化:', newVal)
this.handleScrollChange(newVal)
}
},
methods: {
handleScrollChange(scrollTop) {
// 滚动处理逻辑
}
}
}
</script>
方法四:使用 $parent 引用(不推荐但可用)
子组件
<template>
<view class="component">
<text>通过 $parent 监听</text>
</view>
</template>
<script>
export default {
data() {
return {
parentScrollHandler: null
}
},
mounted() {
// 获取父组件的滚动处理方法
if (this.$parent && this.$parent.handleScroll) {
this.parentScrollHandler = this.$parent.handleScroll.bind(this.$parent)
// 重写父组件方法
this.$parent.handleScroll = (e) => {
this.parentScrollHandler(e)
this.onParentScroll(e)
}
}
},
beforeDestroy() {
// 恢复原始方法
if (this.parentScrollHandler) {
this.$parent.handleScroll = this.parentScrollHandler
}
},
methods: {
onParentScroll(e) {
console.log('监听到父组件滚动:', e.detail.scrollTop)
}
}
}
</script>
方法五:使用页面生命周期(针对页面级组件)
子组件
<template>
<view class="component">
<text>使用页面生命周期</text>
</view>
</template>
<script>
export default {
data() {
return {
scrollTop: 0
}
},
// 监听页面滚动(只有在页面中的组件才有效)
onPageScroll(e) {
this.scrollTop = e.scrollTop
console.log('页面滚动:', e.scrollTop)
},
methods: {
// 自定义处理逻辑
handleScroll(scrollTop) {
// 你的滚动处理代码
}
}
}
</script>
性能优化建议
- 节流处理:避免频繁的滚动事件处理
// 工具函数
export const throttle = (func, delay) => {
let timeoutId
return function(...args) {
if (!timeoutId) {
timeoutId = setTimeout(() => {
func.apply(this, args)
timeoutId = null
}, delay)
}
}
}
// 在组件中使用
methods: {
handleScroll: throttle(function(scrollTop) {
// 处理逻辑,每100ms最多执行一次
}, 100)
}
- 按需监听:不需要时移除监听器
- 避免复杂计算:在滚动处理中避免复杂DOM操作
总结
- 推荐使用方法一(Props传递),结构清晰,易于维护
- 复杂项目可使用方法二(事件总线)或方法三(Vuex)
- 避免使用方法四,除非特殊情况
- 注意性能优化,特别是移动端滚动性能