Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 精品在线99,亚洲精品高清国产麻豆专区,国产精品天天操

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          「Vue項(xiàng)目」中的滾動(dòng)組件&聯(lián)動(dòng)效果從0到1(建議收藏)


          最近的一個(gè)項(xiàng)目做的是vue組件中的一個(gè)應(yīng)用,「處理滾動(dòng)列表」,這個(gè)應(yīng)該是很常見的需求了,在項(xiàng)目中遇到的痛點(diǎn),難點(diǎn),如何一步步解決的,以及小細(xì)節(jié)一些優(yōu)化。

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


          GitHub倉(cāng)庫(kù)

          效果

          處理滾動(dòng)列表最終效果

          從最終效果來看,實(shí)現(xiàn)了三個(gè)我的難點(diǎn)

          • 第一個(gè)就是右側(cè)快速入口,點(diǎn)擊一個(gè)字母跳轉(zhuǎn)到相應(yīng)的部分
          • 左上角的那個(gè)title是跟隨字母一起修改內(nèi)容的
          • 向下滾動(dòng)的話,會(huì)隨時(shí)刷新右側(cè)快速路口以及左上角字母title

          接下來就是一步步去實(shí)現(xiàn),優(yōu)化上面的效果


          第三方庫(kù)介紹

          better-scroll 移動(dòng)端滾動(dòng)的解決方案

          vue-lazyload 圖片懶加載

          基本上實(shí)現(xiàn)上面的效果就是基于這兩個(gè)第三方庫(kù)

          「better-scroll基本使用」

          經(jīng)常會(huì)遇到的問題就是初始化了,「還是不能滾動(dòng)」。那么對(duì)于這個(gè)而言,我最近用到一些經(jīng)驗(yàn)是什么呢?

          我們先看常見的html結(jié)果

          <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>
          復(fù)制代碼

          滾動(dòng)的原理是什么

          better-scroll原理說明

          wrapper是父容器,它一定要有「固定高度」,content是內(nèi)容區(qū)域,它是父元素的第一個(gè)元素,它c(diǎn)ontent會(huì)隨著內(nèi)容的大小撐開而撐高,只有這個(gè)高度大于wrapper父容器高度時(shí),才會(huì)出現(xiàn)滾動(dòng),也就是它的原理。

          那么我們?cè)趺慈コ跏蓟?/p>

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

          點(diǎn)這里有文檔

          接下來就開始把


          從0到1完成

          scroll組件

          這個(gè)scroll組件是子組件,也可以算是個(gè)base組件,完成日常滾動(dòng)的效果

          <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
                  }
                  // 調(diào)用Scroll實(shí)例,表現(xiàn)可以滑動(dòng)
                  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,重新計(jì)算高度
                  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: {
                // 監(jiān)聽到數(shù)據(jù)的變化,就會(huì)重新去refresh數(shù)據(jù),重新去計(jì)算響應(yīng)的數(shù)據(jù)
                data() {
                  setTimeout(() => {
                    this.refresh()
                  }, this.refreshDelay)
                }
              }
            }
          </script>
          <style scoped lang="stylus" rel="stylesheet/stylus">
          </style>
          復(fù)制代碼

          在父組件中導(dǎo)入即可

          完成列表滾動(dòng)

          listview組件導(dǎo)入

          <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>
          復(fù)制代碼

          然后導(dǎo)入scroll組件即可,看看效果

          處理滾動(dòng)列表-實(shí)現(xiàn)列表滾動(dòng)

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

          右側(cè)快速入口

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

          點(diǎn)擊右側(cè)快速路口的話,會(huì)跳轉(zhuǎn)到相應(yīng)的title去,使用的方法就是

          scrollElement

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

          「這個(gè)方法很方便的解決了我們第一個(gè)難點(diǎn)」,現(xiàn)在就差獲取右側(cè)快速路口的索引值了

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

          :data-index="index"
          復(fù)制代碼

          這樣子每次就可以獲取當(dāng)前的索引值

          有了索引值,我們就可以直接調(diào)用srcollToElement()方法,完成左側(cè)的跳轉(zhuǎn)效果。

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

          我們看看效果吧下

          處理滾動(dòng)列表-實(shí)現(xiàn)點(diǎn)擊右側(cè)跳轉(zhuǎn)相應(yīng)位置

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

          @touchmove.stop.prevent="onShortcutTouchMove"
          // 兩個(gè)修飾符阻止冒泡以及默認(rèn)的事件
          復(fù)制代碼

          思路

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

          看代碼

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

          效果怎么樣呢,基本上點(diǎn)擊和手勢(shì)移動(dòng)都較為完美的實(shí)現(xiàn)了。

          處理滾動(dòng)列表-實(shí)現(xiàn)手勢(shì)移動(dòng)右側(cè)跳轉(zhuǎn)相應(yīng)位置

          左右聯(lián)動(dòng)

          左右聯(lián)動(dòng)的效果指的是左側(cè)點(diǎn)擊到某個(gè)區(qū)域,緊接著右側(cè)快速路口也跳轉(zhuǎn)到相應(yīng)位置,這里其實(shí)指的就是高亮效果。

          效果就是滑動(dòng)列表,右側(cè)的字母會(huì)相應(yīng)的高亮,達(dá)到同步的作用,難點(diǎn)是什么呢?

          ListGroup計(jì)算高度

          從圖片上面看,我們發(fā)現(xiàn)每個(gè)listGroup分組里面的成員是不固定的,所以我們?cè)趺慈カ@取到相應(yīng)的currentIndex呢?

          「我們可以獲取到每次滾動(dòng)的距離,那怎么樣去獲取相應(yīng)的currentIndex呢,比如滑到K分組時(shí),currentIndex是對(duì)應(yīng)的下標(biāo)?」

          有個(gè)不錯(cuò)的思路:

          • 我們?nèi)ゾS護(hù)一個(gè)height[i]數(shù)組,該數(shù)組含義就是第i個(gè)分組的范圍是height[i]~~heigth[i+1]
          • 那么我們獲取到滾動(dòng)Y軸的距離,那么就可以確定它所在的范圍,如果滾動(dòng)的距離在posY>height[i]&&posY<height[i+1],那么currentIndex就可以取值i,這樣子好像行。

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

           _calculateHeight() {
                // 這個(gè)方法就是計(jì)算每個(gè)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);
                }
              },
          復(fù)制代碼

          這個(gè)listHeigth數(shù)據(jù)就是我們維護(hù)的第i個(gè)分組的clientHeight距離

          第二步,我們監(jiān)控這個(gè)scrollY,這個(gè)變量表示的就是滾動(dòng)的距離

          watch: {
              // 每次去watch這個(gè)滾動(dòng)的距離,
              scrollY(newY) {
                const listHeight = this.listHeight;
                // 當(dāng)滾動(dòng)到頂部,newY>0
                if (newY > 0) {
                  this.currentIndex = 0;
                  return;
                }
                // 在中間部分滾動(dòng)
                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;
                  }
                }
                // 當(dāng)滾動(dòng)到底部,且-newY大于最后一個(gè)元素的上限
                this.currentIndex = listHeight.length - 2;
              },
          復(fù)制代碼

          這里需要提醒的就是,我們?cè)趺慈ツ玫竭@個(gè)scrollY滾動(dòng)距離呢?

          說到這個(gè),我們得看到scroll組件中,閱讀它的API,會(huì)發(fā)現(xiàn)它提供了on方法,該方法可以去監(jiān)聽該「實(shí)例的鉤子函數(shù)」,所以我們?nèi)?strong>「監(jiān)聽鉤子函數(shù)scroll」

          「scroll鉤子函數(shù)」

          • 參數(shù):{Object} {x, y} 滾動(dòng)的實(shí)時(shí)坐標(biāo)
          • 觸發(fā)時(shí)機(jī):滾動(dòng)過程中。

          所以我們可以通過這個(gè)鉤子來獲取滾動(dòng)的實(shí)時(shí)坐標(biāo)

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

          這樣子我們?cè)谧咏M件scroll中向外派發(fā)一個(gè)scroll事件,并且把「pos = {Object} {x, y} 滾動(dòng)的實(shí)時(shí)坐標(biāo)」向外傳遞,這樣子的話,父組件通過@scroll="scroll" 就可以拿到這個(gè)坐標(biāo)pos

          這樣子我們這個(gè)難點(diǎn)就解決了。

          我們來看看效果

          這樣子基本上問題就解決了,但是呢還會(huì)遇到一個(gè)問題?


          probeType

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

          這個(gè)是文檔上面的內(nèi)容,我們可以看到這個(gè)配置項(xiàng)還是很重要的,我們listview組件需要通過props向子組件傳遞probeType值,值為3,這樣子就可以「在滾動(dòng)中實(shí)時(shí)派發(fā) scroll 事件」

           <scroll :probe-type='3'></scroll>
           // 當(dāng)然了,這個(gè)probeTyep會(huì)在data中拿到
          復(fù)制代碼

          總結(jié)

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

          咱給小編:

          1. 點(diǎn)贊+評(píng)論

          2. 點(diǎn)頭像關(guān)注,轉(zhuǎn)發(fā)給有需要的朋友。

          您的支持是小編不斷輸出的動(dòng)力,謝謝!!

          頁中添加滾動(dòng)字幕效果

          <!DOCTYPE html>

          <html>

          <head>

          <meta charset="utf-8">

          <title>滾動(dòng)字體的設(shè)置</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.找到畫布對(duì)象

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


          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('每天學(xué)習(xí)一點(diǎn)點(diǎn)',x, 553)


          x -= 3

          }else{x=590}



          }, 16)


          </script>


          </body>

          </html>

          頁中實(shí)現(xiàn)像表格文檔那樣固定table的表頭和第一列內(nèi)容,類似于excel表格那樣!下面說說實(shí)現(xiàn)方法

          效果如下:

          在數(shù)據(jù)眾多的列表下,規(guī)定的區(qū)域內(nèi)上下左右都可以滾動(dòng)查看,然而表頭和側(cè)邊表頭都還在,方便用戶查看數(shù)據(jù),增強(qiáng)用戶體驗(yàn)!

          實(shí)現(xiàn)代碼

          html結(jié)構(gòu):

          css代碼:

          javascript代碼:


          主站蜘蛛池模板: 视频精品一区二区三区| 亚洲性日韩精品国产一区二区| 日本精品高清一区二区2021| 国产在线一区二区综合免费视频| 亚洲熟女综合色一区二区三区| 亚洲一区二区三区无码中文字幕 | 久久人妻内射无码一区三区| 日本一区二区三区不卡在线视频| 亚洲中文字幕在线无码一区二区 | 亚洲乱码一区二区三区国产精品| 不卡一区二区在线| 无码少妇一区二区浪潮av| 无码人妻啪啪一区二区| 成人精品一区二区户外勾搭野战| 午夜福利av无码一区二区| 久久精品国产一区| 国产在线精品一区二区不卡| 综合久久久久久中文字幕亚洲国产国产综合一区首 | 波多野结衣中文字幕一区| 欧洲精品码一区二区三区| 亚洲香蕉久久一区二区三区四区| 日韩免费无码一区二区三区| 午夜福利av无码一区二区 | 一区二区三区四区在线视频| 韩国精品一区视频在线播放| 无码国产精品一区二区免费| 亚洲香蕉久久一区二区三区四区| 日韩免费无码一区二区三区| 亚洲宅男精品一区在线观看| 麻豆AV天堂一区二区香蕉| 国产精品电影一区二区三区| 国产一区二区三区在线免费观看| 无码人妻精一区二区三区| 精品视频在线观看一区二区| 无码精品人妻一区二区三区免费 | 久久精品一区二区三区四区| 亚洲一区二区影视| 四虎成人精品一区二区免费网站| 国产在线第一区二区三区| 日本精品一区二区三区四区| 国产午夜毛片一区二区三区|