vue H5列表进入下级页面,返回定位原位置

936 阅读3分钟

问题描述

在电商项目中,通过商品列表进入商品详情是很常见的一个业务场景,在H5的开发过程中,有以下两种情况:

  • 第一种: 列表页面为H5页面,商品详情页是app端原生页,通过jsbridge交互而进入商品详情

  • 第二种:列表页面和商品详情页都是H5页面,我们的技术栈是用vue去实现的,通过vue-router进入详情的H5

在以上两种场景中,第一种模式并不需要我们去做额外的一些处理,直接返回webview,H5页面在不做页面可见性事件的干预下,不会做任何刷新;

第二种场景,则有点区别,通过vue- router跳转,然后通过浏览器的回退事件返回到列表的,列表页会重新走生命周期,并且自动定位到顶部;

解决思路和代码

vue项目的解决方案,自然是通过keep-alive 缓存页面数据,结合scrollBehavior,定位到页面滚动位置;具体处理代码如下:

示例代码为vue3 的代码

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "Home",
    component: () => import(/* webpackChunkName: "Home" */ "../views/Home.vue"),
    meta: {
      title: "列表",
      keepAlive: true
    }
  },
  // 商品详情
  {
    path: "/productDetail",
    name: "ProductDetail",
    component: () =>
      import(/* webpackChunkName: "Home" */ "../views/productDetail.vue"),
    meta: {
      title: "详情",
      showNavigationbar: false,
      keepAlive: false
    }
  }
];
function scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition;
  }
  if (from.meta.keepAlive) {
    from.meta.savedPosition = document.body.scrollTop // 此处为记录的滚动条位置
  }
  from.meta.savedPosition = document.body.scrollTop 
  return { x: 0, y: to.meta.savedPosition || 0 }
}

const router = createRouter({
  history: createWebHashHistory(),
  routes,
  scrollBehavior
});

export default router;

app.vue

// 使用keep-alive 包裹路由组件
<template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <<img src="component" alt="" width="50%" />
        :is="Component"
        :key="$route.name"
        v-if="$route.meta.keepAlive"
      />
    </keep-alive>
    <component
      :is="Component"
      :key="$route.name"
      v-if="!$route.meta.keepAlive"
    />
  </router-view>
</template>

存在的问题

以上则可以解决列表页面的定位问题,但是有一点需要注意的是就是,这里的scrollBehavior更改的页面document的滚动条的高度;

而我们的页面布局是下面这样的:

image.png

如果要将页面的滚动提升到document,那么顶部和底部的两块fix的区域则需要使用,position: fixed; 定位在页面上;而fixed的布局在IOS会存在很多滚动上的体验问题;

image.png

最终方案

根据上面的解决思路,在不改变页面布局的情况下,最终使用keep-alive 缓存页面,scrollBehavior,控制的是documen的滚动,如果需要进行页面内部某一个块的滚动,则需要结合element.scrollTop,(⚠️:element.scrollTop在12.2机型上不生效,需要引入对应的polyfill)

具体步骤:

  1. 使用keep-alive 缓存页面
  2. 引入scrollTo 的polyfill
  3. 在点击商品进入商品详情的位置,记录当前点击的位置,并缓存在本地,然后在onActivated中去除这个位置,并将相关的scroll 的容器滚动到这个位置;
  4. 高度位置的数据使用完成之后,需要清除本地的数据;

完整代码

route.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "Home",
    component: () => import(/* webpackChunkName: "Home" */ "../views/Home.vue"),
    meta: {
      title: "列表",
      keepAlive: true
    }
  },
  // 商品详情
  {
    path: "/productDetail",
    name: "ProductDetail",
    component: () =>
      import(/* webpackChunkName: "Home" */ "../views/productDetail.vue"),
    meta: {
      title: "商祥",
      keepAlive: false
    }
  }
];
const router = createRouter({
  history: createWebHashHistory(),
  routes
});

export default router;

app.vue

<template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component
        :is="Component"
        :key="$route.name"
        v-if="$route.meta.keepAlive"
      />
    </keep-alive>
    <component
      :is="Component"
      :key="$route.name"
      v-if="!$route.meta.keepAlive"
    />
  </router-view>
</template>

scrollToPolyfill.js

(function(doc, win, el) {
  /**
   * @description scrollTo/scrollBy的polyfill
   *              IE9+
   */

  if (!win.scrollTo) {
    win.scrollTo = function(x, y) {
      win.pageXOffset = x;
      win.pageYOffset = y;
    };
  }
  if (!win.scrollBy) {
    win.scrollBy = function(x, y) {
      win.pageXOffset += x;
      win.pageYOffset += y;
    };
  }
  if (!doc.body.scrollTo) {
    el.prototype.scrollTo = function(x, y) {
      this.scrollLeft = x;
      this.scrollTop = y;
    };
  }
  if (!doc.body.scrollBy) {
    el.prototype.scrollBy = function(x, y) {
      this.scrollLeft += x;
      this.scrollTop += y;
    };
  }
})(document, window, Element);

main.js

import "@/utils/scrollToPolyfill.js";

useScroll : 位置记录 & 滚动方法

import { ref, reactive, onMounted, onBeforeMount, Ref } from "vue";
import { useRoute } from "vue-router";

interface ScrollOptions {
  setPosition: (type: number) => void;
  setScrollTop: (type: number) => void;
  restPosition: () => void;
  scrollRef: Ref;
}

export default function(): ScrollOptions {
  const scrollRef = ref(null);
  const route = useRoute();

  const setPosition = (type = TYPE.SCROLL_TYPE.HOME) => {
    const top = scrollRef?.value?.scrollTop
    if (route.meta.keepAlive) {
      window.sessionStorage.setItem("TOC_TOP_POSITION", top);
    }
  };

  const setScrollTop = () => {
    try {
      const top = window.sessionStorage.getItem("TOC_TOP_POSITION");

      if (top) {

          scrollRef?.value?.scrollTo({
            top: parseFloat(top) || 0
          });
        restPosition();
      }
    } catch (e) {
      console.log(e);
    }
  };

  const restPosition = () => {
    window.sessionStorage.removeItem("TOC_TOP_POSITION");
  };

  return {
    scrollRef,
    setPosition,
    setScrollTop,
    restPosition
  };
}

Home.vue

<container ref="scrollRef" ></container>

// 点击进入详情页
handleToProductDetail() {
  setPosition()
}

onActivated(() => {
  setScrollTop();
});