整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          「Vue項目」中的滾動組件&聯動效果從0到1(建議收藏)


          最近的一個項目做的是vue組件中的一個應用,「處理滾動列表」,這個應該是很常見的需求了,在項目中遇到的痛點,難點,如何一步步解決的,以及小細節一些優化。

          借鑒某課的思路,仿QQ音樂效果,記錄一下,自己字母解決這個難題,分享給你們,「希望對你們做移動端滾動列表問題有所幫助」


          GitHub倉庫

          效果

          處理滾動列表最終效果

          從最終效果來看,實現了三個我的難點

          • 第一個就是右側快速入口,點擊一個字母跳轉到相應的部分
          • 左上角的那個title是跟隨字母一起修改內容的
          • 向下滾動的話,會隨時刷新右側快速路口以及左上角字母title

          接下來就是一步步去實現,優化上面的效果


          第三方庫介紹

          better-scroll 移動端滾動的解決方案

          vue-lazyload 圖片懶加載

          基本上實現上面的效果就是基于這兩個第三方庫

          「better-scroll基本使用」

          經常會遇到的問題就是初始化了,「還是不能滾動」。那么對于這個而言,我最近用到一些經驗是什么呢?

          我們先看常見的html結果

          <div class="wrapper">
            <ul class="content">
              <li>...</li>
              <li>...</li>
              ...
            </ul>
            <!-- you can put some other DOMs here, it won't affect the scrolling
          </div>
          復制代碼

          滾動的原理是什么

          better-scroll原理說明

          wrapper是父容器,它一定要有「固定高度」,content是內容區域,它是父元素的第一個元素,它content會隨著內容的大小撐開而撐高,只有這個高度大于wrapper父容器高度時,才會出現滾動,也就是它的原理。

          那么我們怎么去初始化呢

          import BScroll from '@better-scroll/core'
          let wrapper = document.querySelector('.wrapper')
          let scroll = new BScroll(wrapper,{})
          //{}配置一些信息
          復制代碼

          點這里有文檔

          接下來就開始把


          從0到1完成

          scroll組件

          這個scroll組件是子組件,也可以算是個base組件,完成日常滾動的效果

          <template>
            <div ref="wrapper">
              <slot></slot>
            </div>
          </template>
          
          <script type="text/ecmascript-6">
            import BScroll from 'better-scroll'
          
            export default {
              props: {
                probeType: {
                  type: Number,
                  default: 1
                },
                click: {
                  type: Boolean,
                  default: true
                },
                listenScroll: {
                  type: Boolean,
                  default: false
                },
                data: {
                  type: Array,
                  default: null
                },
                pullup: {
                  type: Boolean,
                  default: false
                },
                beforeScroll: {
                  type: Boolean,
                  default: false
                },
                refreshDelay: {
                  type: Number,
                  default: 20
                }
              },
              mounted() {
                setTimeout(() => {
                  this._initScroll()
                }, 20)
              },
              methods: {
                // 初始化Scroll
                _initScroll() {
                  // 判斷是否初始化
                  if (!this.$refs.wrapper) {
                    return
                  }
                  // 調用Scroll實例,表現可以滑動
                  this.scroll = new BScroll(this.$refs.wrapper, {
                    probeType: this.probeType,
                    click: this.click
                  })
                  if (this.listenScroll) {
                    let me = this
                    this.scroll.on('scroll', (pos) => {
                      me.$emit('scroll', pos)
                    })
                  }
                  if (this.pullup) {
                    this.scroll.on('scrollEnd', () => {
                      if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
                        this.$emit('scrollToEnd')
                      }
                    })
                  }
                  if (this.beforeScroll) {
                    this.scroll.on('beforeScrollStart', () => {
                      this.$emit('beforeScroll')
                    })
                  }
                },
                disable() {
                  this.scroll && this.scroll.disable()
                },
                enable() {
                  this.scroll && this.scroll.enable()
                },
                refresh() {  // 刷新scroll,重新計算高度
                  this.scroll && this.scroll.refresh()
                },
                scrollTo() {
                  this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
                },
                scrollToElement() {
                  this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
                }
              },
              watch: {
                // 監聽到數據的變化,就會重新去refresh數據,重新去計算響應的數據
                data() {
                  setTimeout(() => {
                    this.refresh()
                  }, this.refreshDelay)
                }
              }
            }
          </script>
          <style scoped lang="stylus" rel="stylesheet/stylus">
          </style>
          復制代碼

          在父組件中導入即可

          完成列表滾動

          listview組件導入

          <template>
            <scroll
              :listen-scroll="listenScroll"
              :probe-type="probeType"
              :data="data"
              class="listview"
              ref="listview"
            >
              <ul>
                <li v-for="(group,index) in data" class="list-group" ref="listGroup" :key="index">
                  <h2 class="list-group-title">{{group.title}}</h2>
                  <uL>
                    <li
                      @click="selectItem(item)"
                      v-for="(item, index) in group.items"
                      class="list-group-item"
                      :key="index"
                    >
                      <img class="avatar" v-lazy="item.avatar" />
                      <span class="name">{{item.name}}</span>
                    </li>
                  </uL>
                </li>
              </ul>
            </scroll>
          </template>
          復制代碼

          然后導入scroll組件即可,看看效果

          處理滾動列表-實現列表滾動

          上面在listview組件中導入scroll組件,完成基本的列表滾動效果,接下來,完善一步一步效果吧。

          右側快速入口

          <div
                class="list-shortcut"
                @touchstart="onShortcutTouchStart"
                @touchmove.stop.prevent="onShortcutTouchMove"
              >
              <!-- data-index方便獲取一個列表中的index -->
                <ul>
                  <li
                    v-for="(item, index) in shortcutList"
                    :data-index="index"
                    class="item"
                    :class="{'current':currentIndex===index}"
                    :key="index"
                  >{{item}}</li>
                </ul>
              </div>
          復制代碼

          點擊右側快速路口的話,會跳轉到相應的title去,使用的方法就是

          scrollElement

          scrollToElement(el, time, offsetX, offsetY, easing)

          「這個方法很方便的解決了我們第一個難點」,現在就差獲取右側快速路口的索引值了

          給每一個li增加一個data-index屬性名稱,值為index下

          :data-index="index"
          復制代碼

          這樣子每次就可以獲取當前的索引值

          有了索引值,我們就可以直接調用srcollToElement()方法,完成左側的跳轉效果。

          this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0);
          // 這個index就是獲取到下標索引值,然后通過這個
          // 這個第二個參數是滾動的動畫的時間,我們默認為0就行,文檔上面也有專門的說明,可以去看看。
          復制代碼

          我們看看效果吧下

          處理滾動列表-實現點擊右側跳轉相應位置

          接下來完成「touchMove事件」,我們綁定到div上

          @touchmove.stop.prevent="onShortcutTouchMove"
          // 兩個修飾符阻止冒泡以及默認的事件
          復制代碼

          思路

          • 首先要監聽touchStart事件一開始錨點,也就是anchorIndex,還有保存e.touches[0].pageY, y軸上的位置信息,記作y1
          • 監聽touchuMove事件,保存y軸距離,記為y2,這個時候y2-y1就是y軸上的距離變化dataChange
          • 將這個距離dataChange除以高度,這里的高度,我選擇的是每個li的content+padding高度,這個高度的話,正好是整個一個li元素高度,我覺得很合理,delta = dataChange/ANCHOR_HEIGHT
          • 最后一開始的anchorIndex加上delta,就是最新的錨點,這個anchorIndex一定要取證,因為獲取的可能是字符串。

          看代碼

           onShortcutTouchStart(e) {
                  // 獲取到右側的列表索引值
                let anchorIndex = getData(e.target, "index");
                let firstTouch = e.touches[0];
                this.touch.y1 = firstTouch.pageY;   // 計入一開始y軸上的位置
                this.touch.anchorIndex = anchorIndex;   // 保存了每次點擊的錨點
                this._scrollTo(anchorIndex);
              },
              // 監聽的是TouchMove事件
              onShortcutTouchMove(e) {
                let firstTouch = e.touches[0];
                this.touch.y2 = firstTouch.pageY;
                // 滾動的兩個差值 也就是y軸上的偏移
                // 除以每個高度,這樣子的話,就知道偏移了幾個錨點
                let delta = ((this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT) | 0;
                let anchorIndex = parseInt(this.touch.anchorIndex) + delta;
          
                this._scrollTo(anchorIndex);
              },
              
          復制代碼

          效果怎么樣呢,基本上點擊和手勢移動都較為完美的實現了。

          處理滾動列表-實現手勢移動右側跳轉相應位置

          左右聯動

          左右聯動的效果指的是左側點擊到某個區域,緊接著右側快速路口也跳轉到相應位置,這里其實指的就是高亮效果。

          效果就是滑動列表,右側的字母會相應的高亮,達到同步的作用,難點是什么呢?

          ListGroup計算高度

          從圖片上面看,我們發現每個listGroup分組里面的成員是不固定的,所以我們怎么去獲取到相應的currentIndex呢?

          「我們可以獲取到每次滾動的距離,那怎么樣去獲取相應的currentIndex呢,比如滑到K分組時,currentIndex是對應的下標?」

          有個不錯的思路:

          • 我們去維護一個height[i]數組,該數組含義就是第i個分組的范圍是height[i]~~heigth[i+1]
          • 那么我們獲取到滾動Y軸的距離,那么就可以確定它所在的范圍,如果滾動的距離在posY>height[i]&&posY<height[i+1],那么currentIndex就可以取值i,這樣子好像行。

          那么我們按照上面的思路來完善吧

           _calculateHeight() {
                // 這個方法就是計算每個listGroup高度
                this.listHeight = [];
                const list = this.$refs.listGroup;
                let height = 0;
                this.listHeight.push(height);
                for (let i = 0; i < list.length; i++) {
                  let item = list[i];
                  height += item.clientHeight;
                  this.listHeight.push(height);
                }
              },
          復制代碼

          這個listHeigth數據就是我們維護的第i個分組的clientHeight距離

          第二步,我們監控這個scrollY,這個變量表示的就是滾動的距離

          watch: {
              // 每次去watch這個滾動的距離,
              scrollY(newY) {
                const listHeight = this.listHeight;
                // 當滾動到頂部,newY>0
                if (newY > 0) {
                  this.currentIndex = 0;
                  return;
                }
                // 在中間部分滾動
                for (let i = 0; i < listHeight.length - 1; i++) {
                  let height1 = listHeight[i];
                  let height2 = listHeight[i + 1];
                  if (-newY >= height1 && -newY < height2) {
                    this.currentIndex = i;
                    this.diff = height2 + newY;
                    return;
                  }
                }
                // 當滾動到底部,且-newY大于最后一個元素的上限
                this.currentIndex = listHeight.length - 2;
              },
          復制代碼

          這里需要提醒的就是,我們怎么去拿到這個scrollY滾動距離呢?

          說到這個,我們得看到scroll組件中,閱讀它的API,會發現它提供了on方法,該方法可以去監聽該「實例的鉤子函數」,所以我們去「監聽鉤子函數scroll」

          「scroll鉤子函數」

          • 參數:{Object} {x, y} 滾動的實時坐標
          • 觸發時機:滾動過程中。

          所以我們可以通過這個鉤子來獲取滾動的實時坐標

          if (this.listenScroll) {
                    let me = this
                    this.scroll.on('scroll', (pos) => {
                      // me指的就是實例
                      // 通過監聽scroll事件,有一個回調,pos是一個對象,有x,y軸的具體距離
                      // 去派發一個scroll事件,這樣子外部也就是父組件可以拿到我們的pos
                      me.$emit('scroll', pos)
                    })
                  }
          復制代碼

          這樣子我們在子組件scroll中向外派發一個scroll事件,并且把「pos = {Object} {x, y} 滾動的實時坐標」向外傳遞,這樣子的話,父組件通過@scroll="scroll" 就可以拿到這個坐標pos

          這樣子我們這個難點就解決了。

          我們來看看效果

          這樣子基本上問題就解決了,但是呢還會遇到一個問題?


          probeType

          • 類型:Number
          • 默認值:0
          • 可選值:1、2、3
          • 作用:有時候我們需要知道滾動的位置。當 probeType 為 1 的時候,會非實時(屏幕滑動超過一定時間后)派發scroll 事件;當 probeType 為 2 的時候,會在屏幕滑動的過程中實時的派發 scroll 事件;當 probeType 為 3 的時候,不僅在屏幕滑動的過程中,而且在 momentum 滾動動畫運行過程中實時派發 scroll 事件。如果沒有設置該值,其默認值為 0,即不派發 scroll 事件。

          這個是文檔上面的內容,我們可以看到這個配置項還是很重要的,我們listview組件需要通過props向子組件傳遞probeType值,值為3,這樣子就可以「在滾動中實時派發 scroll 事件」

           <scroll :probe-type='3'></scroll>
           // 當然了,這個probeTyep會在data中拿到
          復制代碼

          總結

          • 解決難點一,獲取滾動的實時位置,通過子組件scroll實例on方法,去「監聽鉤子函數scroll」,然后向外去派發一個scroll函數,并且把滾動的距離傳給父組件listview
          • 解決難點二,獲取到滾動Y軸的距離,就可以進一步去判斷,它是落在哪一個listGroup中,也就是哪一個分組中,這樣子就確定currentIndex。
          • 通過watch監聽scrollY值,表示Y軸滾動距離,發生變化時,更新currentIndex。
          • 有了currentIndex,在判斷currentIndex === index,就可以實現高亮效果
          • 還有一些BetterScroll 提供的API 比如refresh(),重新計算 BetterScroll,當 DOM 結構發生變化的時候務必要調用確保滾動的效果正常scrollToElement(el, time, offsetX, offsetY, easing) 滾動到指定的目標元素on(type, fn, context) 監聽當前實例上的鉤子函數。如:scroll、scrollEnd 等
          • 還有一個收獲就是用第三方API,「有問題一定要查文檔」

          咱給小編:

          1. 點贊+評論

          2. 點頭像關注,轉發給有需要的朋友。

          您的支持是小編不斷輸出的動力,謝謝!!

          頁中添加滾動字幕效果

          <!DOCTYPE html>

          <html>

          <head>

          <meta charset="utf-8">

          <title>滾動字體的設置</title>

          </head>

          <body>

          <canvas id="canvas1" width="600" height="600" style="border:1px solid #000000"></canvas>

          <script type="text/javascript">

          var canvas1 = document.querySelector("#canvas1") // 1.找到畫布對象

          var ctx = canvas1.getContext("2d") // 2.上下文對象(畫筆)


          ctx.shadowBlur = 10; // 陰影距離

          ctx.shadowColor = "red" // 陰影顏色

          ctx.shadowOffsetX = 30 // 陰影偏移

          ctx.shadowOffsetY = 30 // 陰影偏移


          ctx.font = "150px 楷體"


          ctx.fillText("你好!", 20,150)


          ctx.fillText("你好!", 20,350)


          ctx.strokeText('你好!',23, 153)


          ctx.strokeText('你好',23, 553)


          canvas繪制文字



          var x = 600

          setInterval(function(){

          if(x > -350){

          //清空畫布

          ctx.clearRect(0,0,600,600)

          ctx.strokeText('你好!',x, 153)

          ctx.fillText("你好!", x,350)


          ctx.font = "50px 宋體"

          ctx.strokeText('每天學習一點點',x, 553)


          x -= 3

          }else{x=590}



          }, 16)


          </script>


          </body>

          </html>

          頁中實現像表格文檔那樣固定table的表頭和第一列內容,類似于excel表格那樣!下面說說實現方法

          效果如下:

          在數據眾多的列表下,規定的區域內上下左右都可以滾動查看,然而表頭和側邊表頭都還在,方便用戶查看數據,增強用戶體驗!

          實現代碼

          html結構:

          css代碼:

          javascript代碼:


          主站蜘蛛池模板: 国产伦精品一区二区免费| 亚洲AV本道一区二区三区四区| 国产香蕉一区二区三区在线视频 | 无码精品久久一区二区三区 | 亚洲国产欧美国产综合一区| 欧美日韩国产免费一区二区三区| 北岛玲在线一区二区| 久久国产精品一区| 国产一区二区在线观看视频| 中文字幕在线观看一区二区 | 中文字幕无码免费久久9一区9| 免费无码毛片一区二区APP| 国产精品无码一区二区三区不卡| 熟妇人妻系列av无码一区二区 | 伊人久久大香线蕉AV一区二区 | 一区二区三区视频观看| 亚洲国产精品无码第一区二区三区| 中文字幕精品一区| 无码aⅴ精品一区二区三区| 69福利视频一区二区| 丝袜人妻一区二区三区网站 | 一区二区三区人妻无码 | V一区无码内射国产| 国产一区在线观看免费| 亚洲一区二区三区91| 亚州日本乱码一区二区三区| 中文字幕在线无码一区| 精品亚洲一区二区三区在线播放| 国产精品电影一区二区三区| 久久青青草原一区二区| 人妻av综合天堂一区| 欧美一区内射最近更新| 国产一区二区三区小说| 在线免费视频一区| 国产一区二区三区精品久久呦| 亚洲av无码一区二区三区天堂| 国产一区二区在线观看app| 午夜无码视频一区二区三区| 国产精品第一区揄拍无码| 韩国一区二区三区| 伊人色综合视频一区二区三区|