vue 組件的 scrollBehavior

瀏覽器對用戶訪問網頁的記錄
  • 在聊如何管理vue組件滾動行為之前诵冒,先簡單說說(畢竟深入了我也很模糊o(╯□╰)o)瀏覽器是如何對用戶訪問過的頁面的保持劝篷,瀏覽器歷史記錄是對用戶所訪問的頁面按時間順序進行的記錄和保存,以上是MDN對瀏覽器就如何跟蹤用戶訪問過網頁的解釋性說明。
  • 通常我們很少會對頁面回退或前進進行操作,在瀏覽器用戶界面上提供有前進崖疤、回退按鈕嗜闻,頁面跳轉到離開頁面之前的位置蜕依,而不是重新刷新頁面,這個功能是由瀏覽器引擎(與渲染引擎琉雳、解析引擎概念不同)來完成的样眠。當用戶進入一個頁面的時候,會往 history 棧中放入當前的記錄翠肘,對頁面級別的操作通過操作內置對象 history 可以滿足一些需求檐束。
vue對訪問記錄的管理
  • 進入正題,vue 路由跳轉就是通過對 history.pushState()history.replaceState() 方法的模擬來實現束倍,會往 history 棧中存放一條記錄被丧,這也是為什么 vuerouter.push 方法只能在支持 history.pushState() 方法的瀏覽器中使用,當調用 router.go() 或者 router.back() 方法的時候就和 history.go()肌幽、history.back() 效果一樣晚碾,都是對 history 棧中的記錄進行訪問,上述行為與通過瀏覽器的回退和前進效果也是一樣喂急。
    但是格嘁,在不加處理的情況下,組件的滾動行為會跟我們想象的不同廊移。
vue組件滾動行為
  • 設置三個路由 /home糕簿、/list探入、/about,即對應三個不同的組件懂诗,
  <ul class="tab">
      <li>
        <router-link to="/" >首頁</router-link>
      </li>
      <li>
        <router-link to="/list" >列表</router-link>
      </li>
      <li>
        <router-link to="/about" >關于</router-link>
      </li>
      <li>
        <a href="#" @click='() => { this.$router.back() }'>點擊回退</a>
      </li>
  </ul>
  <router-view></router-view>
  • 每個組件的結構都是 ul>li 的結構
  <!-- 以 home 組件為例 -->
  <ul class="list_content home">
     <li v-for='i in 10'>{{ i }}</li>
  </ul>

長這樣:


請注意蜂嗽,現在開始滾動首頁位置至第 5 屏的位置,當切換到列表以及關于頁面的時候殃恒,會發(fā)現這兩個頁面的滾動行為和首頁滾動行為一致植旧。


既不涉及組件的緩存,也不涉及組件的復用离唐,我們不禁會疑惑為什么首頁的滾動會影響到其他兩個頁面病附,如果我們有當切換組件的時候,需要讓當前組件的內容是從 scrollTop = 0 的時候開始瀏覽亥鬓,那這樣的結果將會是一個絆腳石完沪。
原因如下,因為基于SPA模式開發(fā)嵌戈,所以頁面僅有一個覆积,實現頁面切換是利用哈希組件的映射關系,vue-router 是通過哈希來模擬完整的 url熟呛,但是對于頁面來說仍是一個 url宽档,所以在任何一個組件滾動頁面,切換到其他組件的時候惰拱,頁面仍保持滾動之前的狀態(tài)雌贱,這就是出現上述現象的原因。

如何管理組件的滾動行為
  • 如果你是想簡單粗暴的在每次切換組件的時候讓頁面回到頂部偿短,router.beforeEach() 導航守衛(wèi)會是一個不錯的選擇:
    router.beforeEach((to, from, next) => {
        // 讓頁面回到頂部
        document.documentElement.scrollTop = 0
        // 一定不要忘記調用 next()
        next()
    })

但這不是我們的主題,要借助 vue-router 提供的 scrollBehavior馋没,來管理組件滾動行為昔逗。

  • 關于 scrollBehavior,這里貼出官網對概念的介紹 傳送門篷朵,當然勾怒,借助 scrollBehavior,你也能讓頁面在組件切換的時候回到頂部:
  const scrollBehavior = function (to, from, savedPosition) {
        // savedPosition 會在你使用瀏覽器前進或后退按鈕時候生效
       // 這個跟你使用 router.go() 或 router.back() 效果一致
       // 這也是為什么我在 tab 欄結構中放入了一個 點擊回退 的按鈕
       if (savedPosition) {
            return savedPosition
          } else {
            // 如果不是通過上述行為切換組件声旺,就會讓頁面回到頂部
            return {x: 0, y: 0}
        }
    }
  • 上述會定制所有組件的滾動行為笔链,但有時候我們希望,當用戶在瀏覽 home 頁面到底部的時候腮猖,跳轉到 list 頁面瀏覽鉴扫,當瀏覽到中間的時候,跳轉到 about 頁面瀏覽澈缺,當用戶每次回退的時候坪创,都希望保持離開之前頁面的狀態(tài)炕婶,即:從 about 回退到 list 頁面的時候,頁面仍是在中間莱预,回退到 home 頁面的時候柠掂,仍是在底部,這就需要我們個性化定制依沮。
定制不同組件的scrollBehavior

這里用到路由的元信息 meta 更細顆粒度控制滾動行為涯贞,這里以 home 組件為例說明:

      const Home = {
          template: `
            <ul class="list_content home">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `,
          data () {
              return {
                  timerId: ''
              }
           },
          mounted () {
                 // 通過 addEventListener 方法注冊事件的時候需要格外小心      
                // 如果在 destroyed 鉤子函數中沒有銷毀 scroll 事件
                // 在激活 home 組件的時候會再次綁定 scroll 事件
                // window.addEventListener('scroll', this.justifyPos)
                // 通過 on 方式綁定事件能夠有效避免上述情況
                window.onscroll = this.justifyPos
            },
          methods: {
                justifyPos () {
                    // 節(jié)流;
                    if (this.timerId) clearTimeout(this.timerId)
                    this.timerId = setTimeout(() => {
                        // 獲取頁面滾動距離之后設置給當前路由的 元信息
                        this.$route.meta.y = window.pageYOffset
                    }, 300)
                }
            },
          destroyed () {
                // 當組件銷毀的時候危喉,移除滾動行為監(jiān)聽, 清空定時器肩狂;
                // 該方法是綁定到 window 身上,即使跳轉到其他組件姥饰,仍然會監(jiān)聽頁面的滾動行為
                // window.removeEventListener('scroll', this.justifyPos)
                // clearTimeout(this.timerId)
            }
        }
        const List = {
            template: `
            <ul class="list_content list">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `
        }
        const About = {
            template: `
            <ul class="list_content about">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `
        }
        const routes = [
                // 設置 meta傻谁,細顆粒控制組件滾動
                {path: '/', component: Home, meta: {x: 0, y: 0}},
                {path: '/list', component: List, meta: {x: 0, y: 0}},
                {path: '/about', component: About, meta: {x: 0, y: 0}}
            ]
        const scrollBehavior = function (to, from, savedPosition) {
            return to.meta
        }
        const router = new VueRouter({
            routes,
            scrollBehavior,
            linkExactActiveClass: 'current'
        })

上述會在 home 組件滾動停止的時候記錄當前組件的滾動位置信息列粪,并且存儲到對應 home 組件的路由 meta 這個對象中审磁,當切換到 list 或者 about 頁面之后在回到 home 組件,仍會保留著離開之前的位置岂座,而不是簡單地讓頁面回到頂部态蒂。
但是,你會發(fā)現你只是針對 home 組件的滾動行為進行控制费什,listabout 組件的滾動行為也能夠實現個性化定制钾恢,即也會將當前組件的滾動行為記錄在對應的路由 meta 中。
這會讓人疑惑鸳址,因為在 listabout 組件中并沒有設置 justifyPos 方法瘩蚪,并且 window.onscroll = this.justifyPosthis 綁定到當前的上下文中。
vue 官網對于組件銷毀介紹稿黍,會解綁所有的指令以及事件監(jiān)聽疹瘦,但是對于方法的引用處理沒有提到,個人覺得在這里應該拋出警告或者錯誤的巡球,但是 vue 卻沒有提示言沐,這也是令我困惑的一點。但是酣栈,這卻為滾動行為監(jiān)聽提供了更好的處理方法险胰,那就是綁定到 vue 根實例上,而不是某一個單一組件上矿筝,因為 this 會自動綁定到當前上下文:

    new Vue({
        router,
        data: {
            timerId: ''
        },
        mounted () {
            window.addEventListener('scroll', this.justifyPos)
        },
        methods: {
            justifyPos () {
                if (this.timerId) clearTimeout(this.timerId)
                this.timerId = setTimeout(() => {
                    this.$route.meta.y = window.pageYOffset
                }, 300)
            }
        }
    }).$mount('#app')
當better-scroll(以下簡稱bs)遇上vue起便,如何定制滾動行為
  • 貼上傳送門 better-scroll,感興趣的可以看一下。
  • 之所以會談到 bs缨睡,如果在項目中用到該插件鸟悴,那么頁面滾動行為跟組件滾動行為是完全不一樣的,這是因為 bs 特殊的結構要求奖年,父容器需要有個固定的高度细诸,所有的滾動行為是由子元素來產生的,在移動端應用 bs陋守,通常會將父容器的高度設置為屏幕的高度震贵,你的所有應用都應該放到這個父容器內。bs 在移動端性能很出色水评,但是這卻為組件個性化定制 scrollBehavior 帶來了一些小麻煩猩系。
  • 原因就是應用 bs 插件的組件,一般會設置高度和屏幕高度一致中燥,這樣即使通過 meta 來設置滾動記錄寇甸,在 vue-routerscrollBehavior 中返回 meta 也沒有用處,因為高度是定死了疗涉,就不存在滾動拿霉,你所看到的滾動式是 bs 插件所處理的。
  • 這時候咱扣,就需要用到 bs 提供的一些事件和方法了绽淘,仍以 home 組件為例說明,看代碼:
    const Home = {
            template: `
            <div class="wrapper" ref="wrapper">
                <ul class="list_content home">
                    <li v-for='i in 10' @click='goList'>{{ i }}</li>
                </ul>
            </div>
            `,
            mounted () {
                this.$nextTick(() => {
                    // 初始化 BS
                    this._initScroll()
                    // 滾動監(jiān)聽
                    this.scroll.on('scrollEnd', (pos) => {
                        // 將滾動信息設置給當前路由元信息
                        this.$route.meta.y = pos.y
                    })
                    // 當前組件激活的時候闹伪,滾動到離開前位置
                    // 如果你想要滾動動畫效果沪铭,可以在 scrollTo 方法中自定義
                    this.scroll.scrollTo(0, this.$route.meta.y, 0)
                })
            },
            methods: {
                _initScroll () {
                    if (!this.$refs.wrapper) return
                    this.scroll = new BScroll(this.$refs.wrapper, {
                        mouseWheel: {
                            speed: 20,
                            invert: false,
                            easeTime: 300
                        },
                        // 派發(fā) click 事件;
                        click: true
                    })
                },
                // 跳轉到列表頁偏瓤;
                goList () {
                    this.$router.push({name: 'list'})
                }
            }
        }
        const List = {
            template: `
            <ul class="list_content list">
                <li v-for='i in 10' @click='goHome'>{{ i }}</li>
            </ul>
            `,
            methods: {
                // 回跳到首頁
                goHome () {
                    this.$router.push({name: 'home'})
                }
            }
        }
        const routes =  [
                // 設置 meta
                {path: '/', name: 'home', component: Home, meta: {x: 0, y: 0}},
                {path: '/list', name: 'list', component: List, meta: {x: 0, y: 0}},
                {path: '/about', component: About, meta: {x: 0, y: 0}}
            ]
        // scrollBehavior 其實這里已經沒有什么作用了杀怠,因為當前組件的高度被定死和整個屏幕一樣高
        // const scrollBehavior = function (to, from, savedPosition) {
        //    return to.meta
        // }
        // 設置路由
        const router = new VueRouter({
            routes,
            scrollBehavior,
            linkExactActiveClass: 'current'
        })
        // 掛載
        new Vue({router}).$mount('#app')
  • 通過 bs 提供的事件以及方法再結合路由的 meta,也能夠實現細顆粒度控制滾動硼补,如果對組件使用了 keep-alive驮肉,你應該在每次切換到該組件的時候在 activated 鉤子函數中初始化 bsscrollEnd 事件以及 scrollTo 方法已骇;如果你頁面有分頁的功能,你可能需要在分頁邊界花費一些心思如何讓滾動行為跨越分頁票编,這里建議是使用組件緩存褪储,關于組件如何清除緩存,可以參考我的另一篇文章 組件去緩存慧域,當然如果你有更好的處理方式鲤竹,也可以留言。
寫在最后
  • 上文中有個 節(jié)流 的概念,這里貼出參考文獻辛藻,有興趣的可以瀏覽一下碘橘,節(jié)流防抖 的不同在哪:節(jié)流與防抖
  • 本文為原創(chuàng)文章,如果需要轉載吱肌,請注明出處痘拆,方便溯源,如有錯誤地方氮墨,可以在下方留言纺蛆,歡迎校勘规揪,源碼已上傳到我的GitHub桥氏。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猛铅,隨后出現的幾起案子字支,更是在濱河造成了極大的恐慌,老刑警劉巖奸忽,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕伪,死亡現場離奇詭異,居然都是意外死亡月杉,警方通過查閱死者的電腦和手機刃跛,發(fā)現死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苛萎,“玉大人桨昙,你說我怎么就攤上這事‰缜福” “怎么了蛙酪?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翘盖。 經常有香客問我桂塞,道長,這世上最難降的妖魔是什么馍驯? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任阁危,我火速辦了婚禮,結果婚禮上汰瘫,老公的妹妹穿的比我還像新娘狂打。我一直安慰自己,他們只是感情好混弥,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布趴乡。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晾捏。 梳的紋絲不亂的頭發(fā)上蒿涎,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音惦辛,去河邊找鬼劳秋。 笑死,一個胖子當著我的面吹牛裙品,可吹牛的內容都是我干的俗批。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼市怎,長吁一口氣:“原來是場噩夢啊……” “哼岁忘!你這毒婦竟也來了?” 一聲冷哼從身側響起区匠,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤干像,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驰弄,有當地人在樹林里發(fā)現了一具尸體麻汰,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年戚篙,在試婚紗的時候發(fā)現自己被綠了五鲫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡岔擂,死狀恐怖位喂,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情乱灵,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站抒蚜,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蛹稍,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奉芦。 院中可真熱鬧声功,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑞眼。三九已至,卻和暖如春徒像,著一層夾襖步出監(jiān)牢的瞬間次慢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猾蒂。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親时捌。 傳聞我的和親對象是個殘疾皇子奢讨,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容