uniapp组件中如何监听父页面的滚动事件?

在 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>

性能优化建议

  1. 节流处理:避免频繁的滚动事件处理
// 工具函数
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)
}
  1. 按需监听:不需要时移除监听器
  2. 避免复杂计算:在滚动处理中避免复杂DOM操作

总结

  • 推荐使用方法一(Props传递),结构清晰,易于维护
  • 复杂项目可使用方法二(事件总线)方法三(Vuex)
  • 避免使用方法四,除非特殊情况
  • 注意性能优化,特别是移动端滚动性能

Comments

No comments yet. Why don’t you start the discussion?

发表回复