踩坑日记(一天半的踩坑经历)Vue3+Vant4 记录滑动位置的坑

出现问题搜索大半天 这方面的实在是少的可怜 没办法 只能硬搞了

问题背景
在使用 keep-alive 缓存组件时,组件的状态会被保留,但滚动位置不会自动恢复。因此,我们需要手动记录和恢复滚动位置

Vue.vue 

<template>
  <div id="app">
    <keep-alive :exclude="['ChatPage']">
      <component :is="layout" >
        <!-- 这里是当前布局的路由视图 -->
        <router-view></router-view>
      </component>
    </keep-alive>

layout绑定的是多个layout组件 通过路由跳转可以使用到多个layout 其中的layout中也可以继续使用keep-alive来缓存组件

如其中一个layout:

<template>
    <div class="content">
      <router-view v-slot="{ Component }">
        <keep-alive :exclude="['ComponentC']">
          <component :is="Component"/>
        </keep-alive>
      </router-view>
    </div>
<template/>

根据路由不同又可以缓存不同组件

接下来是我需要被记录滚动转态的帖子组件 我希望它能在切换其他组件和其它组件返回到这个帖子组件时能恢复离开时的状态

  <div ref="wrapper">
    <van-list  v-for="blog in props1.blogList" :key="blog.id" class="blog-card" @click="toBlog1(blog.id)">
      <!-- 用户信息部分 -->
      <div class="blog-header">
        <van-row gutter="20">
          <van-col span="3">
            <van-image round :src="blog.user.avatar" height="43px" width="43px" />
          </van-col>
          <van-col span="4">
            <div class="user-name">{{ blog.user.name }}</div>
            <div class="auth-status">
              <span v-if="blog.user.isVerified">认证</span>
              <span v-else>未认证</span>
            </div>
          </van-col>
        </van-row>
      </div>

      <!-- 博客内容部分 -->
      <div class="blog-content">
        <div class="blog-title">{{ blog.title }}</div>
        <div class="blog-description">{{ blog.content }}</div>
        <div class="blog-image" v-if="blog.coverImage">
          <van-image :src="blog.coverImage" width="100%" height="200px" fit="cover" />
        </div>
      </div>

      <!-- 底部操作部分 -->
      <div class="blog-footer">
        <van-row gutter="20">
          <van-col span="12">
            <div class="comments">
              <van-icon name="chat-o" size="20" color="#888" @click.stop="toComments(blog.id)" />
              <span>{{ blog.commentsNum }} 评论</span>
            </div>
          </van-col>
          <van-col span="12">
            <div class="likes">
              <van-icon :name="blog.isLike ? 'thumb-circle' : 'thumb-circle-o'" color="blue" size="20" @click.stop="toggleLike(blog)" />
              <span>{{ blog.likedNum }} 点赞</span>
            </div>
          </van-col>
        </van-row>
      </div>

    </van-list>
  </div>

看了其他踩坑帖子 有说用监听器来记录滚动位置的 有的使用点击帖子时记录滚动位置 如下

vue3使用vant4的列表vant-list点击进入详情自动滚动到对应位置,踩坑日记(一天半的踩坑经历)_vant4 list-优快云博客vue3使用vant4的列表vant-list点击进入详情自动滚动到对应位置,踩坑日记(一天半的踩坑经历)_vant4 list-优快云博客

但是都离开不了一个中心点 就是如何获取当前滚动元素的状态来记录

可偏偏如何获取滚动元素成为了难点 我有试过在list元素上绑定滚动事件 或者直接获取list元素的scrollTop 但都无济于事 里面的值不是空就是0 根本不能使用 原因可能是我寻找可滚动元素失败或不对 

最后终于找到了解决方案 Vant4给我们提供了一个可以查找最近可滚动的元素的函数useEventListener useScrollParent - Vant 4 (vant-ui.github.io)

完美解决找不到滚动元素的坑 

思路如下:

1、定义滚动容器的引用:
使用 ref 定义 wrapper,并将其类型指定为 HTMLElement | null,以确保类型安全。
2、获取滚动父元素:
使用 useScrollParent 获取 wrapper 的滚动父元素。
3、监听滚动事件:
使用 useEventListener 监听 scroll 事件,并在控制台输出相关信息。
4、组件激活时恢复滚动位置:
在 onActivated 钩子中,检查 scrollParent 是否为 Element 对象。如果是,则将其 scrollTop 属性设置为保存的值;否则,使用 window.scrollTo 方法恢复滚动位置。
5、路由离开前保存滚动位置:
在 onBeforeRouteLeave 钩子中,检查 scrollParent 是否为 Element 对象。如果是,则保存其 scrollTop 属性值;否则,保存 window 的滚动位置。

主要代码如下:

// 定义滚动容器的引用
const wrapper = ref<HTMLElement>();

// 获取滚动父元素
const scrollParent = useScrollParent(wrapper);

// 用于保存滚动位置的变量
const savedScrollTop = ref(0);

// 监听滚动事件
useEventListener(
    'scroll',
    () => {
      console.log('scroll');
      console.log(`scrollParent:`, scrollParent);
      console.log("scrollParent.scrollTop: "+scrollParent.value!.scrollTop);
    },
    { target: scrollParent },
);

// 组件激活时恢复滚动位置
onActivated(() => {
  if (scrollParent.value instanceof Element) {
    scrollParent.value.scrollTop = savedScrollTop.value;
  } else {
    window.scrollTo(0, savedScrollTop.value);
  }
  console.log("blogcardlist被激活了");
  console.log("savedScrollTop: " + savedScrollTop.value);
});

// 路由离开前保存滚动位置
onBeforeRouteLeave(() => {
  if (scrollParent.value instanceof Element) {
    savedScrollTop.value = scrollParent.value.scrollTop;
  } else {
    savedScrollTop.value = window.pageYOffset || document.documentElement.scrollTop;
  }
  console.log("blogcardlist路由离开了");
  console.log("savedScrollTop: " + savedScrollTop.value);
});

这里还有一个坑 使用组件缓存的生命周期函数来记录离开时的状态是保存不了数据的 因此这里使用onBeforeRouteLeave

完整代码如下:

<template>
  <div ref="wrapper">
    <van-list v-for="blog in props1.blogList" :key="blog.id" class="blog-card" @click="toBlog1(blog.id)">
      <!-- 用户信息部分 -->
      <div class="blog-header">
        <van-row gutter="20">
          <van-col span="3">
            <van-image round :src="blog.user.avatar" height="43px" width="43px" />
          </van-col>
          <van-col span="4">
            <div class="user-name">{{ blog.user.name }}</div>
            <div class="auth-status">
              <span v-if="blog.user.isVerified">认证</span>
              <span v-else>未认证</span>
            </div>
          </van-col>
        </van-row>
      </div>

      <!-- 博客内容部分 -->
      <div class="blog-content">
        <div class="blog-title">{{ blog.title }}</div>
        <div class="blog-description">{{ blog.content }}</div>
        <div class="blog-image" v-if="blog.coverImage">
          <van-image :src="blog.coverImage" width="100%" height="200px" fit="cover" />
        </div>
      </div>

      <!-- 底部操作部分 -->
      <div class="blog-footer">
        <van-row gutter="20">
          <van-col span="12">
            <div class="comments">
              <van-icon name="chat-o" size="20" color="#888" @click.stop="toComments(blog.id)" />
              <span>{{ blog.commentsNum }} 评论</span>
            </div>
          </van-col>
          <van-col span="12">
            <div class="likes">
              <van-icon :name="blog.isLike ? 'thumb-circle' : 'thumb-circle-o'" color="blue" size="20" @click.stop="toggleLike(blog)" />
              <span>{{ blog.likedNum }} 点赞</span>
            </div>
          </van-col>
        </van-row>
      </div>
    </van-list>
  </div>
</template>

<script setup lang="ts">
import { ref, onActivated, onBeforeRouteLeave } from 'vue';
import { useScrollParent } from 'some-scroll-parent-library'; // 假设这是你使用的库
import { useEventListener } from '@vueuse/core';

// 定义滚动容器的引用
const wrapper = ref<HTMLElement | null>(null);

// 获取滚动父元素
const scrollParent = useScrollParent(wrapper);

// 用于保存滚动位置的变量
const savedScrollTop = ref(0);

// 监听滚动事件
useEventListener(
  'scroll',
  () => {
    console.log('scroll');
    console.log(`scrollParent:`, scrollParent);
    console.log("scrollParent.scrollTop: " + scrollParent.value?.scrollTop);
  },
  { target: scrollParent },
);

// 组件激活时恢复滚动位置
onActivated(() => {
  if (scrollParent.value instanceof Element) {
    scrollParent.value.scrollTop = savedScrollTop.value;
  } else {
    window.scrollTo(0, savedScrollTop.value);
  }
  console.log("blogcardlist被激活了");
  console.log("savedScrollTop: " + savedScrollTop.value);
});

// 路由离开前保存滚动位置
onBeforeRouteLeave((to, from, next) => {
  if (scrollParent.value instanceof Element) {
    savedScrollTop.value = scrollParent.value.scrollTop;
  } else {
    savedScrollTop.value = window.pageYOffset || document.documentElement.scrollTop;
  }
  console.log("blogcardlist路由离开了");
  console.log("savedScrollTop: " + savedScrollTop.value);
  next();
});
</script>

<style scoped>
/* 你的样式代码 */
</style>

这样切换其他组件在返回的时候就不会从头开始啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值