【问题标题】:How to listen to the window scroll event in a VueJS component?如何在 VueJS 组件中监听窗口滚动事件?
【发布时间】:2018-01-31 00:09:33
【问题描述】:

我想在我的 Vue 组件中监听窗口滚动事件。到目前为止,这是我尝试过的:

<my-component v-on:scroll="scrollFunction">
    ...
</my-component>

在我的组件方法中定义了scrollFunction(event),但它似乎不起作用。

有人知道怎么做吗?

谢谢!

【问题讨论】:

  • 参见forum.vuejs.org/t/…:“自定义组件,只是一个调用语法信标,不是可以发出事件但可以从其中转发事件的组件。只有原生组件才能发出原生事件。”

标签: javascript vue.js vuejs2


【解决方案1】:

根据我的经验,在滚动上使用事件侦听器会由于管道进入该事件流而产生大量噪音,如果您正在执行庞大的 handleScroll 函数,这可能会导致性能问题。

我经常在评分最高的答案中使用此处显示的技术,但我在其之上添加了去抖动,通常大约 100ms 会产生良好的性能与用户体验的比率。

这是一个使用评分最高的答案并添加了 Lodash debounce 的示例:

import debounce from 'lodash/debounce';

export default {
  methods: {
    handleScroll(event) {
      // Any code to be executed when the window is scrolled
      this.isUserScrolling = (window.scrollY > 0);
      console.log('calling handleScroll');
    }
  },

  mounted() {
    this.handleDebouncedScroll = debounce(this.handleScroll, 100);
    window.addEventListener('scroll', this.handleDebouncedScroll);
  },

  beforeDestroy() {
    // I switched the example from `destroyed` to `beforeDestroy`
    // to exercise your mind a bit. This lifecycle method works too.
    window.removeEventListener('scroll', this.handleDebouncedScroll);
  }
}

尝试将100 的值更改为0 和1000,这样您就可以看到调用handleScroll 的方式/时间的区别。

奖励:您还可以使用像 vue-scroll 这样的库以更简洁和可重用的方式完成此操作。如果您还没有看过 Vue 中的自定义指令,这是一个很好的用例。查看https://github.com/wangpin34/vue-scroll。

这也是 Sarah Drasner 在 Vue 文档中的一个很棒的教程:https://vuejs.org/v2/cookbook/creating-custom-scroll-directives.html

对于 Vue 3 用户

在 vue3 中你应该使用 unmounted 或 beforeUnmount,而不是 beforeDestroy。

Lifecycle hook beforeDestroy is not emitted in Vue3

【讨论】:

  • 你真的需要lodash吗?在handleScroll 函数中有一个setTimeout 并在超时ID 不为空时清除它就可以解决问题。无论如何,两者都有效,但我们可以避免使用 2 行代码的库
  • 我的意图是突出差异。这主要取决于您的 handleScroll 函数内部的确切内容,当然还有客户端执行它的速度。一个不好的例子是一个缓慢的客户端执行线程阻塞代码,执行需要 200 毫秒,而 handleScroll 每 100 毫秒调用一次。
  • 注意vue3中应该使用unmounted或者beforeUnmount,stackoverflow.com/questions/62743811/…
【解决方案2】:

结合this.$vuetify.breakpoint.name 和按需加载,滚动事件是一个非常有用的功能。

使用触发器。例如,一个选项卡:

<v-tabs
  v-bind:grow="!isFullScreen()"
  v-bind:vertical="isFullScreen()"
>

一些类属性:

private isUserScrolling: boolean = false;
private isLoaded: boolean = false;
private startScroll: number = 3;

对触发器做出反应的功能(必要时进行调整):

private isFullScreen(): boolean {
    switch (this.$vuetify.breakpoint.name) {
      case "xs":
        this.startScroll = 500;
        return false;
      case "sm":
        this.startScroll = 300;
        return false;
      case "md":
        this.startScroll = 100;
        return true;
      case "lg":
        this.startScroll = 50;
        return true;
      case "xl":
        this.startScroll = 3;
        return true;
    }
}

添加您的活动:

created() {
   window.addEventListener("scroll", this.handleScroll);
}

对您的活动做出反应:

private handleScroll(event: any): void {
   this.isUserScrolling = window.scrollY > this.startScroll;
   
   if (this.isUserScrolling && !this.isLoaded) {
     // load your stuff
     ...
     this.isLoaded = true;
   }
}

【讨论】:

    【解决方案3】:

    这样的事情呢?顺便说一下,这是 Vue 3

    setup() {
        function doOnScroll(event) {
          window.removeEventListener("scroll", doOnScroll);
          console.log("stop listening");
          // your code here ....
          setTimeout(() => {
            window.addEventListener("scroll", doOnScroll, { passive: true });
            console.log("start listening");
          }, 500);
        }
    
        window.addEventListener("scroll", doOnScroll, { passive: true });
      }
    

    这里的想法是只侦听一次滚动事件,执行您的脚本,然后在 setTimeout 函数中延迟再次连接滚动侦听器。如果在此延迟之后页面仍在滚动,则将再次处理滚动事件。 基本上每 500 毫秒(在本例中)只监听一次滚动事件。

    我使用它只是为了在滚动过程中添加一个 css 类以移开按钮。

    【讨论】:

      【解决方案4】:
      document.addEventListener('scroll', function (event) {
          if ((<HTMLInputElement>event.target).id === 'latest-div') { // or any other filtering condition
        
          }
      }, true /*Capture event*/);
      

      您可以使用它来捕获事件,这里的“latest-div”是 id 名称,因此您可以根据 id 在此处捕获所有滚动操作,您也可以在此处执行操作。

      【讨论】:

        【解决方案5】:

        这不会刷新你的组件我通过使用 Vux 解决了这个问题 为 vuex“页面”创建一个模块

        export const state = {
            currentScrollY: 0,
        };
        
        export const getters = {
            currentScrollY: s => s.currentScrollY
        };
        
        export const actions = {
            setCurrentScrollY ({ commit }, y) {
                commit('setCurrentScrollY', {y});
            },
        };
        
        export const mutations = {
            setCurrentScrollY (s, {y}) {
               s.currentScrollY = y;
            },
        };
        
        export default {
            state,
            getters,
            actions,
            mutations,
        };
        
        

        在 App.vue 中:

        created() {
            window.addEventListener("scroll", this.handleScroll);
          },
          destroyed() {
            window.removeEventListener("scroll", this.handleScroll);
          },
          methods: {
            handleScroll () {
              this.$store.dispatch("page/setCurrentScrollY", window.scrollY);
              
            }
          },
        

        在您的组件中:

          computed: {
            currentScrollY() {
              return this.$store.getters["page/currentScrollY"];
            }
          },
        
          watch: {
            currentScrollY(val) {
              if (val > 100) {
                this.isVisibleStickyMenu = true;
              } else {
                this.isVisibleStickyMenu = false;
              }
            }
          },
        

        而且效果很好。

        【讨论】:

          【解决方案6】:

          我知道这是一个老问题,但我找到了Vue.js 2.0+ Custom Directives 的更好解决方案:我也需要绑定滚动事件,然后我实现了这个。

          首先,使用@vue/cli,将自定义指令添加到src/main.js(在Vue.js 实例之前)或您启动它的任何位置:

          Vue.directive('scroll', {
            inserted: function(el, binding) {
              let f = function(evt) {
                if (binding.value(evt, el)) {
                  window.removeEventListener('scroll', f);
                }
              }
              window.addEventListener('scroll', f);
            }
          });
          

          然后,将自定义 v-scroll 指令添加到要绑定的元素和/或组件。当然你必须插入一个专门的方法:我在我的例子中使用了handleScroll。

          <my-component v-scroll="handleScroll"></my-component>
          

          最后,将你的方法添加到组件中。

          methods: {
            handleScroll: function() {
              // your logic here
            }
          }
          

          您不必再关心 Vue.js 的生命周期,因为自定义指令本身就可以。

          【讨论】:

          • TypeError: binding.value 不是 Vue 2.5.2 的函数。
          • handleScroll 函数声明应包含事件和元素参数
          【解决方案7】:

          这是直接与 Vue 自定义组件一起使用的内容。

           <MyCustomComponent nativeOnScroll={this.handleScroll}>
          

          或

          <my-component v-on:scroll.native="handleScroll">
          

          并为handleScroll定义一个方法。简单!

          【讨论】:

          • nativeOnScroll 到底是什么?
          • 使用 Vue 自定义组件时,如果你想启动任何原生事件,比如点击、滚动、更改——你需要使用原生的。 onChange 或 onScroll 不起作用。作为一个简单的例子,想象一个&lt;MyButton v-on:click="thisWontWork" /&gt; 但&lt;MyButton v-on:click.native="thisWorks" /&gt;
          • 是的,native 事件修饰符有时是必须的。我的印象是,被提及的nativeOnScroll 不仅仅是孩子发出的自定义事件。对不起
          • 非常感谢!该解决方案对我来说比定义事件侦听器更好!
          • @kushalvm 您能否为nativeOnScroll 添加完整示例?我认为它类似于&lt;div @scroll='$emit('nativeOnScroll')&gt;...&lt;/div&gt; 内的MyCustomComponent。在您的回答中,它给人的印象是它是一些内置的 Vue 事件。
          【解决方案8】:

          其实我找到了解决办法。我在创建组件时在scroll 事件上添加事件侦听器,并在组件销毁时移除事件侦听器。

          export default {
            created () {
              window.addEventListener('scroll', this.handleScroll);
            },
            destroyed () {
              window.removeEventListener('scroll', this.handleScroll);
            },
            methods: {
              handleScroll (event) {
                // Any code to be executed when the window is scrolled
              }
            }
          }
          

          希望这会有所帮助!

          【讨论】:

          • 不销毁会怎样?它会在所有页面上工作吗?
          • @AlexanderKim 正确。即使组件不再可见,它也会继续触发您提供的方法。
          • @AlexanderKim 如果您正在使用 keep-alive 组件,并且您希望它在不活动时停止,您应该使用 deactivate 而不是destroy,并使用 activate 而不是 created。这也应该使用mounted而不是created,因为即使创建它也不能保证被安装。
          • @jeerbl 是否还有其他事情尚未完成。我试图在 handleScroll 函数中控制一些东西,但什么也没发生。
          • 我也遇到了同样的问题。 HandleScroll 方法没有任何反应。对于 vue js 建议任何方法。
          【解决方案9】:

          我认为最好的方法就是添加“.passive”

          v-on:scroll.passive='handleScroll'
          

          【讨论】:

          • 虽然对组件本身有效,但它只会检测组件内部的滚动。如果您想检测整个页面滚动,您需要使用window.addEventListener。查看其他答案。
          【解决方案10】:

          我多次需要此功能,因此我将其提取到mixin。可以这样使用:

          import windowScrollPosition from 'path/to/mixin.js'
          
          new Vue({
            mixins: [ windowScrollPosition('position') ]
          })
          

          这会在 Vue 实例上创建一个反应式 position 属性(可以任意命名)。该属性以[x,y] 数组的形式包含窗口滚动位置。

          随意使用this CodeSandbox demo。

          这是 mixin 的代码。它有详尽的注释,因此不难理解它是如何工作的:

          function windowScrollPosition(propertyName) {
            return {
              data() {
                return {
                  // Initialize scroll position at [0, 0]
                  [propertyName]: [0, 0]
                }
              },
              created() {
                // Only execute this code on the client side, server sticks to [0, 0]
                if (!this.$isServer) {
                  this._scrollListener = () => {
                    // window.pageX/YOffset is equivalent to window.scrollX/Y, but works in IE
                    // We round values because high-DPI devies can provide some really nasty subpixel values
                    this[propertyName] = [
                      Math.round(window.pageXOffset),
                      Math.round(window.pageYOffset)
                    ]
                  }
          
                  // Call listener once to detect initial position
                  this._scrollListener()
          
                  // When scrolling, update the position
                  window.addEventListener('scroll', this._scrollListener)
                }
              },
              beforeDestroy() {
                // Detach the listener when the component is gone
                window.removeEventListener('scroll', this._scrollListener)
              }
            }
          }
          

          【讨论】:

          • 将此答案与去抖动(见下文)结合起来,您就有了可靠的解决方案!谢谢
          • @Visualize 这在很大程度上取决于你想在滚动上做什么。只要你不执行非常昂贵的任务,你实际上甚至不需要去抖——在一个 3 岁的中低端 Android 手机(荣耀 5C)上测试了这个演示:vue-scroll-fuicczvgsv.now.sh跨度>
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-20
          • 2018-04-29
          • 2020-04-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多