返回定位(back position)优化坎坷之路

390 阅读2分钟

前言

背景

  • 操作步骤
    • 1 商品列表页和详情是使用ssr渲染, 商品列表页面一直滚动使用分页加载,
    • 2 从列表去详情,详情返回无法定位离开位置,需要解决的问题就是定位到列表上次离开的地方,(safari back 可以定位到对应位置,非safari back 无法定位到位置)

  • 方法尝试
    • 1 使用sessionStorage配合scrollTo 、setInterval
      • 1.1页面离开保存 scrollTop值到sessionStorage
      • 1.2下次进入调用 scrollTo 到对应位置,
      • 1.3如果scrollTo一次无法滚动到对应的位置,使用setInterval滚动到对应位置后,触发view more 直到滚动到对应位置或者10秒超时停止
    • 2 使用localStorage结合sessionStorage
      • 2.1页面离开保存 scrollTop值到sessionStorage,保存数据到 localStorage
      • 2.2下次进入,取localStorage的数据、取sessionStorage中 scrollTop值
      • 2.3在domready的时候,重新写列表数据,并执行scrollTo到对应位置
    • 3 使用indexDB结合sessionStorage
      • 3.1页面离开保存 scrollTop值到sessionStorage,保存数据到 indexDB
      • 3.2下次进入,取indexDB的数据、取sessionStorage中 scrollTop值
      • 3.3在domready的时候,重新写列表数据,并执行scrollTo到对应位置

坎坷之路1

1使用sessionStorage配合scrollTo 、setInterval

// 页面离开监听保存  

useEffect(() => {
   function saveDataList(){
   const onBeforeUnload = (event) => {
   const scrollPos = { x: window.scrollX, y: window.scrollY }
   sessionStorage.setItem(url, JSON.stringify(scrollPos))
 }
 window.addEventListener('beforeunload', onBeforeUnload)
 
 function scrollTo(scrollPosition: any) {
       let index = 0
       if (scrollPosition) {
           const footerEle = document.getElementById('layout_footer')
           const scrollLoadingEle = document.getElementById('scrollLoading')
           const layoutEle = document.getElementById('layout')
           const footerClientHeight: any =
               (footerEle?.clientHeight as any) * 1 || 0
           if (footerEle) {
               footerEle.style.height = '0px'
               footerEle.style.overflow = 'hidden'
           }
           if (layoutEle) {
               layoutEle.style.opacity = '0'
           }
           if (
               scrollLoadingEle &&
               scrollPosition >
                   (document.body || document.documentElement).clientHeight
           ) {
               scrollLoadingEle.style.display = 'block'
           }

           window.requestAnimationFrame(() => {
               window.scrollTo({
                   top: scrollPosition,
               })
               const times: any = setInterval(() => {
                   if (
                       window.scrollY >= scrollPosition - footerClientHeight ||
                       index > 50
                   ) {
                       index++

                       if (footerEle) {
                           footerEle.style.height = `auto`
                           footerEle.style.minHeight = `${footerClientHeight}px`
                       }
                       window.scrollTo({
                           top: scrollPosition,
                       })
                       if (layoutEle) {
                           layoutEle.style.opacity = '1'
                       }
                       if (scrollLoadingEle) {
                           scrollLoadingEle.style.display = 'none'
                       }
                       clearInterval(times)
                   } else {
                       index++
                       window.scrollTo({
                           top: scrollPosition,
                       })
                   }
               }, 100)
           })
       }
   }
  const scrollPos: any = JSON.parse(sessionStorage.getItem(url) as any)
  scrollTo(scrollPos?.y)
   return () => {
               window.removeEventListener('beforeunload', saveDataList)
           }

})

看下效果图

在滚动量比较小或者加载页数比较小的情况下是可以接受的(也可以配合虚拟加载) Kapture 2021-07-07 at 21.24.30.gif

当列表页面过大的或者也是交多少lodaing时间会很长,不见建议使用

Kapture 2021-07-07 at 21.36.18.gif

坎坷之路2

2 使用localStorage结合sessionStorage 在使用localStorage的时候咱们先了解一下 localStorage最大存储空间

  • 1、localStorage 拓展了 cookie 的 4K 限制。
  • 2、localStorage 会可以将第一次请求的数据直接存储到本地,这个相当于一个 5M 大小的针对于前端页面的数据库,相比于 cookie 可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的。 localStorage 的局限
  • 3、浏览器的大小不统一,并且在 IE8 以上的 IE 版本才支持 localStorage 这个属性。
  • 4、目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换。
  • 5、localStorage在浏览器的隐私模式下面是不可读取的。
  • 6、localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡。
  • 7、localStorage不能被爬虫抓取到。localStorage 与 sessionStorage 的唯一一点区别就是 localStorage 属于永久性存储,而 sessionStorage 属于当会话结束的时候,sessionStorage 中的键值对会被清空。

看伪代码

  • 1 就是在离开页面的 操作
    • 1.1 sessionStorage.setItem(url, JSON.stringify(scrollPos))
    • 1.2 slocalStorage.setItem(url, JSON.stringify(dataList))
  • 2 再次进入该页面
    • 2.1 const scrollPos= sessionStorage.getItem(url, JSON.stringify(scrollPos))
    • 2.2 const dataList= slocalStorage.getItem(url, JSON.stringify(dataList))
    • 2.3 把页面的数据替换成dataList,并滚动到对应位置 看gif动图,秒开,但是当数据量比较打的时候就会出现异常导致存储出现问题,所以不是可靠的方案

Kapture 2021-07-07 at 22.07.37.gif

 注意 当dataList过大时就会出现异常
 

坎坷之路3

3 使用indexDB结合sessionStorage,在使用indexDB咱们先了解一下indexDB

  • 1、键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

  • 2、异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

  • 3、支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

  • 4 、同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

  • 5、浏览器的最大存储空间是动态的——它取决于您的硬盘大小。 全局限制为可用磁盘空间的50%。 在Firefox中,一个名为Quota Manager的内部浏览器工具会跟踪每个源用尽的磁盘空间,并在必要时删除数据。

//打开数据库
 var db;
 function useIndexedDB() {
 try {
     function saveDataList() {
         var onBeforeUnload = (event) => {
             var elementListData = $('.className').html()
             setMoreData(elementListData)
             saveScrollPos(location.pathname)
         }

         window.addEventListener('beforeunload', onBeforeUnload)
     }

     // 保存scrollPos
     function saveScrollPos(url) {
         var scrollPos = { x: window.scrollX, y: window.scrollY }
         if (scrollPos) {
             sessionStorage.setItem(url, JSON.stringify(scrollPos))
         }
     }
     // 保存自动滚动 配合indexdb
     var scrollPos = JSON.parse(
         sessionStorage.getItem(window.location.pathname)
     )
     // scrollPos不存在或者scrollPos.y不大于0
     if (!scrollPos || !(scrollPos && scrollPos.y)) {
         scrollLoadingEle.style.display = 'none'
     }
     window.indexedDB =
         window.indexedDB ||
         window.mozIndexedDB ||
         window.webkitIndexedDB ||
         window.msIndexedDB
     if (!window.indexedDB) {
         scrollLoadingEle.style.display = 'none'
         return
     }

     var openReq = window.indexedDB.open('moreDatabase')
     openReq.onerror = function (event) {
         scrollLoadingEle.style.display = 'none'
     }

     openReq.onsuccess = function (event) {
         db = openReq.result
         if (!db.objectStoreNames.contains('moreData')) {
             var objectStore = db.createObjectStore('moreData', {
                 keyPath: 'id',
             })
             objectStore.createIndex('listData', 'listData', {
                 unique: false,
             })
         } else {
             //读取并滚动
             readDB()
         }
         saveDataList()
         useDatabase(db)
         console.log('数据库打开成功')
     }

     openReq.onupgradeneeded = function (event) {
         db = event.target.result
         if (!db.objectStoreNames.contains('moreData')) {
             var objectStore = db.createObjectStore('moreData', {
                 keyPath: 'id',
             })
             objectStore.createIndex('listData', 'listData', {
                 unique: false,
             })
         } else {
             //读取并滚动
             readDB()
         }
         saveDataList()
     }

     function useDatabase(db) {
         db.onversionchange = function (event) {
             if (db.close && typeof db.close === 'function') {
                 db.close()
             }
         }
     }

     function setMoreData(elementListData) {
         try {
             var compressedString = pako.gzip(elementListData, {
                 to: 'string',
             })
             var requestUpdate = db
                 .transaction('moreData', 'readwrite')
                 .objectStore('moreData')
                 [keyWord]({
                     id: location.pathname,
                     listData: compressedString,
                 })
             requestUpdate.onerror = function (event) {}
             requestUpdate.onsuccess = function (event) {
                 if (db.close && typeof db.close === 'function') {
                     console.log('数据库关闭成功')
                     db.close()
                 }
             }
         } catch (error) {
             console.log(error)
         }
     }

     function readDB() {
         try {
             var scrollPos = JSON.parse(
                 sessionStorage.getItem(window.location.pathname)
             )

             var prevPagePathname =
                 sessionStorage.getItem('prevPagePathname')

             if (!/\/products\//g.test(prevPagePathname)) {
                 scrollLoadingEle.style.display = 'none'
                 window.scrollTo({
                     top: 0,
                 })
                 var request = db
                     .transaction('moreData', 'readwrite')
                     .objectStore('moreData')
                     .delete(location.pathname)
                 request.onsuccess = function (event) {}
                 request.onerror = function (event) {}
                 return
             }

             var request = db
                 .transaction('moreData', 'readonly')
                 .objectStore('moreData')
                 .get(location.pathname)
             request.onerror = function (event) {
                 window.scrollTo({
                     top: 0,
                 })
                 scrollLoadingEle.style.display = 'none'
             }
             request.onsuccess = function (event) {
                 if (
                     request &&
                     request.result &&
                     request.result.listData &&
                     pako
                 ) {
                     keyWord = 'put'
                     var listData = pako.ungzip(request.result.listData, {
                         to: 'string',
                     })
                     $('.classaName').html(listData)
                     window.scrollTo({
                         top: scrollPos.y,
                     })
                 } else {
                     window.scrollTo({
                         top: 0,
                     })
                 }
                 scrollLoadingEle.style.display = 'none'
             }
         } catch (error) {
             scrollLoadingEle.style.display = 'none'
         }
     }
 } catch (error) {
     scrollLoadingEle.style.display = 'none'
 }
}
try {
useIndexedDB()

} catch (error) {
scrollLoadingEle.style.display = 'none'
}

优点 相比第一个想法、加载数据会很稳定,数据存储容量大 看图

WechatIMG24.png

Kapture 2021-07-07 at 23.02.40.gif indexDB的使用的坑

* 1 虽然indexDB存储空间很大,但是会对每天数据大小有个限制大致130M
* 2一定要及时清理indexDB,当超过最大的存储浏览器闪退

如有不足,可以留言指出😁