Vue自定义指令实现下拉刷新和上滑加载更多

发布于:2025-02-10 ⋅ 阅读:(68) ⋅ 点赞:(0)

在现代 Web 应用中,下拉刷新和上滑加载更多是常见的用户交互模式,用于提升用户体验。本文将介绍如何使用 Vue 自定义指令,不依赖任何第三方库,仅使用原生 JavaScript 实现这两个功能。

实现原理

下拉刷新:

  1. 监听 touchstart 事件: 记录手指触摸屏幕时的初始垂直坐标 (startY)。
  2. 监听 touchmove 事件: 计算手指移动的距离 (distance = currentY - startY)。
  3. 判断刷新条件: 当 distance 超过预设阈值 (例如 50px) 且列表处于顶部 (scrollTop === 0) 时,触发下拉刷新操作。
  4. 调用刷新方法: 执行自定义指令绑定的回调函数,通常是一个异步操作,例如从服务器获取最新数据。
  5. 重置状态: 刷新操作完成后,重置 startY 和 loading 状态。

上滑加载更多:

  1. 监听 scroll 事件: 监听列表的滚动事件。
  2. 判断加载条件: 当列表滚动到底部附近时 (例如,距离底部 100px),触发加载更多操作。
  3. 调用加载方法: 执行自定义指令绑定的回调函数,通常是一个异步操作,例如从服务器获取更多数据。
  4. 重置状态: 加载操作完成后,重置 loading 状态。

代码实现

<template>
  <div class="list-container" v-pull-refresh="onPullRefresh" v-infinite-scroll="onInfiniteScroll">
    <ul>
      <li v-for="item in items" :key="item">{
  
  { item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`),
      isLoading: false, // Add loading state
      currentPage: 1,
      pullDownDistance: 0
    };
  },
  directives: {
    pullRefresh: {
      mounted(el, binding) {
        let startY = 0;
        let isLoading = false;
        let initialScrollTop = 0;

        el.addEventListener('touchstart', (e) => {
          if (isLoading || el.scrollTop > 0) return;
          startY = e.touches[0].clientY;
          initialScrollTop = el.scrollTop; // Store initial scroll position
        });

        el.addEventListener('touchmove', (e) => {
          if (isLoading ||  el.scrollTop > 0 ) return;

          const currentY = e.touches[0].clientY;
          const distance = currentY - startY;

          if (distance > 0) {
                e.preventDefault(); // Prevent scrolling while pulling down

                el.scrollTop = initialScrollTop - distance * 0.5; // Smooth scrolling resistance
                this.pullDownDistance = distance; // Update pull down distance

                if (distance > 50) { // Trigger refresh
                    isLoading = true;
                    binding.value().finally(() => {
                        isLoading = false;
                        this.pullDownDistance = 0;
                        el.scrollTop = 0; // Reset scrollTop to 0
                    });
                }
            }
        });

        el.addEventListener('touchend', () => {
          if (this.pullDownDistance > 0 && this.pullDownDistance <= 50) {

            el.scrollTop = 0; // Reset scroll position
            this.pullDownDistance = 0; // Reset pull down distance
          }
        });
      },
    },
    infiniteScroll: {
      mounted(el, binding) {
        let isLoading = false;

        el.addEventListener('scroll', () => {
          if (isLoading) return;

          const { scrollTop, scrollHeight, clientHeight } = el;
          if (scrollTop + clientHeight >= scrollHeight - 100) {
            isLoading = true;
            binding.value().finally(() => {
              isLoading = false;
            });
          }
        });
      },
    },
  },
  methods: {
     onPullRefresh() {
      this.isLoading = true;
       return new Promise(resolve => {
         setTimeout(() => {
          this.currentPage = 1;
          this.items = Array.from({ length: 20 }, (_, i) => `Refreshed Item ${i + 1}`);
          this.isLoading = false;
          resolve();
        }, 1000); // Simulate API call delay
      });
    },
    onInfiniteScroll() {
      this.isLoading = true; // Set loading state
      return new Promise((resolve) => {
        setTimeout(() => {
          this.currentPage++;
          const newItems = Array.from({ length: 20 }, (_, i) => `Item ${this.items.length + i + 1}`);
          this.items = [...this.items, ...newItems];
          this.isLoading = false; // Reset loading state after loading
          resolve();
        }, 1000);
      });
    },
  },
};
</script>

<style scoped>
.list-container {
  height: 300px; /* Adjust as needed */
  overflow-y: auto;
}
ul{
    padding: 0;
    margin: 0;
    list-style: none;
}
li {
  padding: 10px;
  border-bottom: 1px solid #ccc;
}
</style>

潜在问题及解决方案

  • 重复触发: 在加载数据时,如果用户继续滚动或下拉,可能会重复触发加载或刷新操作。 解决方案是在指令中添加 loading 状态,并在加载过程中禁用事件监听器或忽略事件。 在上面的代码中,isLoading 变量用于此目的。
  • iOS 弹性滚动: iOS 的弹性滚动可能会干扰下拉刷新功能。 解决方案是在 touchmove 事件处理函数中调用 e.preventDefault(),阻止默认的滚动行为,并在下拉时手动控制滚动位置。
  • 兼容性: 触摸事件 (touchstarttouchmovetouchend) 主要用于移动设备。 对于桌面浏览器,需要添加鼠标事件 (mousedownmousemovemouseup) 的支持。

总结

本文介绍了如何使用 Vue 自定义指令实现下拉刷新和上滑加载更多功能,并提供了一个完整的示例代码。 该方案不依赖任何第三方库,仅使用原生 JavaScript 实现,代码简洁易懂,易于维护。 同时,还讨论了实现过程中可能遇到的问题及相应的解决方案,帮助开发者更好地理解和应用该技术。

补充说明:

  • 你可以根据实际需要调整阈值、加载更多触发距离等参数。
  • 实际应用中,onPullRefresh 和 onInfiniteScroll 方法通常会调用 API 获取数据,并更新列表内容。
  • 为了更好的用户体验,可以在加载过程中显示 loading 指示器。

希望这篇文章能够帮助你理解并实现 Vue 下拉刷新和上滑加载更多功能!


网站公告

今日签到

点亮在社区的每一天
去签到