“这是页面书签,等我回来记得滚回原样奥”

664

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

页面书签

简单讲就是你从当前页面进行滚动浏览,当“出去一趟”再回来的时候,要复原这个滚动位置,就好像一本书的书签一样。

那么就描述下对页面书签的期望吧

  1. 当我跳转其他页面的时候,回来要复原。
  2. 复原不能直接就定位太突兀,应该是动画。

那么开始着手去实现期望

如何实现 “当我跳转其他页面的时候,回来要复原。

顺推一下思路:

  1. “复原”是需要依靠“数据”的。
  2. "数据"是需要在页面之间跳转依然可以“保存”的
  3. “保存”是需要脱离页面之外不受其“控制”的
  4. “控制”是需要谁来做呢🤔???

简单啊,那明显就交给数据仓库被😄。

如何实现 “复原不能直接就定位太突兀,应该是动画。

再顺推一下:

  1. “动画”是需要“细腻”的。
  2. "细腻"是需要“连贯”的。
  3. “连贯”是需要对此执行“递归”的。
  4. “递归”是需要谁来做呢🤔???

经过探索,Bom对象的requestAnimationFrame就很合适。

requestAnimationFrame 官方介绍:

告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

画面一秒60fps,那么回调函数执行次数通常是每秒60次(除了卡掉帧了)那就足够“连贯”了,没次做一点改变,连续做就能做到“细腻”了(这就跟游戏开发没啥区别了)。

这里必须要强调一下,查阅了几篇文章,有不少都说错了一个点(我觉得的)

比如这句:

你这么说就好像跟setinterval似的了,我觉得不对,逻辑矛盾了。

我的理解:页面一秒60fps,那么你执行了requestAnimationFrame,那么只是在下一次屏幕绘制的时候执行一次,那么怎么连贯的呢,那是通过递归执行这个函数实现的,代码就是这么说的。不会骗人的。

上代码:

export function sineaseOut(t, b, c, d) {
    return c * ((t = t / d - 1) * t * t + 1) + b
}
export function scrollToView(scroller, value) {
    if (!scroller) {
        return
    }

    const scroll = value
    const scrollStart = 0
    let start = null
    const step = (timestamp) => {
        if (!start) {
            start = timestamp
        }
        let stepScroll = sineaseOut(timestamp - start, 0, scroll, 500)
        let total = scroller.scrollTop = scrollStart + stepScroll
        if (total < scrollStart + scroll) {
            requestAnimationFrame(step)
        }
    }
    requestAnimationFrame(step)
}

那么做个小结

  • 状态数据交由数据管理器Mobx来负责,mobxglobal仓库来做。

  • 动画的细腻就通过一秒60次的绘制进行递归调用requestAnimationFrame来实现。


道理都懂,那么然后呢?

哎呀,确实啊。。。虽然两个都有办法解决了,但还有最最关键的问题,“用武之地”在哪啊???

“用武之地”可是很看“风水”的哦

位置要是没选好,那简直让你的代码中处处充满了做作,就为了看起来舒服那么一点,那就跟“打肿脸充胖子”差不多了。

最危险的陷阱往往很温柔

首先从需求上来看,很容易就联想到组件的卸载和挂载,因为真的很匹配,这就是最大的诱惑,因为它能实现,但这绝不是好的办法,至少不是最好的,因为你如果真这么做了,那所有的页面组件都要去写这个重复的逻辑,那太烦了,如果是给一个已经写好了很多页面的项目加,你会崩溃的,这不能接受

要抓住乌云背后的幸福线

当我想来想去,几经波折,差点“真香”的时候,终于让我想到了一个极佳的位置,那就是。

history.listen

那时那刻,我自信地嘴角微微上扬,稳了😄。

直接上代码,我相信b数自在大家心中,无需多言了。

    let history = useHistory();
    let location = useLocation();
    const {
        global: { current, scrollData, changeParams },
    } = store;
    useEffect(() => {
        const { pathname, search } = location;
        changeParams({
            current: decodeURIComponent(pathname + search)
        })
        if (!history._listenCount) {
            history._listenCount++;
            history.listen((locationState, type) => {
                const {
                    global: { current, scrollData, changeParams },
                } = store;
                let node = document.getElementsByClassName('ant-layout-content')[0]
                if (!node) {
                    return
                }
                // node.scrollTop = 0;
                //最新页面的Key,存上就行
                let newkey = decodeURIComponent(locationState.pathname + locationState.search);
                /* 找一下看看有没有记录值 */
                let recordValue = scrollData[newkey]
                if (typeof recordValue === 'number') {
                    scrollToView(node, recordValue)
                }
                // 取值(过去的)存一下,old
                let oldKey = current;
                let scrollDataTemp = { ...toJS(scrollData), [oldKey]: node.scrollTop }
                if (type === "POP") {
                    //POP 情况下node.scrollTop已经被设置成0了
                    changeParams({
                        current: newkey,
                    })
                } else {
                    changeParams({
                        current: newkey,
                        scrollData: scrollDataTemp
                    })
                }
            })
        }
    }, [])

代码很清晰,就是页面走了,记录一下,页面回来,恢复一下,解决的就是这么简单自然。


看下效果


结语

通过探索页面书签功能的实现方式,能够更好的体会一个道理,实现的方式不止一种,清楚自己想要什么,你也就知道自己该坚持什么,该怎么做,也就更有可能抓住乌云背后的幸福线。

集成了该功能的🌰