「Vue项目」我是如何解决滚动组件&联动效果


前语

最近的一个项目做的是vue组件中的一个运用,处理翻滚列表,这个应该是很常见的需求了,在项目中遇到的痛点,难点,怎样一步步处理的,以及小细节一些优化。

学习某课的思路,仿QQ音乐效果,M 3 + } G记载一下,自己字母处理这个难题,共享给你们,希望对你们X 8 / # 做移动端翻滚列表问题有所协助


GitHub库房

效果

「Vue项目」我是怎么处理翻滚组件&联动效果
处理翻滚列表终究效果

从终究效果来看,结束了三个我的难点

  • 榜首个就是右侧快速进口,点击一个字母跳7 z v转到相应的部分
  • 左上角的那个title是跟随字母一起批改内容的
  • 向下翻滚的话,会随时改写右侧快速路口以及左上角字母title

接下来就是一步步去结束,优化上面的效果


第三方库介绍

better-scroll 移动端翻滚的处理计划

vue-lazyload 图片懒加载

根本上结束上面的效果就是根据这两个第三方库

better-scroll根本运用

经常会遇到的问题就是初始化了,仍是不能翻滚。那么对于这个而言,我最近用到一些经验是什么呢?

我们先看常见的html效果J ? $ Y M

&l7 N ! O c V wt;div class="wrapper">
  <ul class="content">
    <li>...</li>
    <li>...</li>7 y G
    ...
  </ul>
  <!-- you can put some oa E [ R m m w 3 Tther DOMs here, it won't affect th% t j K 7 e ee scrolling
</div>

翻滚的原理是什么

「Vue项目」我是怎么处理翻滚组件&联动效果
bettB ` V $ )er-scroll原理说明W J : ( 2 a / P 6

wrapper是父容器,m / J S它必定要有固定高度,content是内容区域,它是父元素的榜首: L $ S J 8 ) h个元素,它content会跟着内容的巨细撑开而撑高,只需这个高度大于wrapper父容器高度时,才会出现翻滚,也就是它的原理。

那么我们怎样去初始化呢

import BScroll from '@better-scroll/core'
let wrapper. 7 4 = document.querySelector('.wrappek x Br')
let scroll = new~ 2 } z w ) f BScroll(wrapper,{})
//{}配备一些信息

点这儿有文档

接下来就l 5 M开始把


从0到1结束

scroll组件

这个scroll组件是子组件,也可以算是个base组件,结束日常翻滚3 / A a o ) |的效果

<template>
  <m O S Z =div ref="wrapper">
    <slot></slot>
  </div>
</template>

<scriV r y @pt type="text/ecmascript-6">
  import BScro- { &ll from 'better-scroll'

  export default {
    props; 8 ] ] T v - p: {
      probeType: {
        type: Number,
        default: 1
      },2 H j 8
      click: {
        type: Boolean,
        default: true
      },
      lisQ C I e ,tenScroll: {
        type: Boolean,
        default: false
      },
      data: {
        type: Array,
        default: null
      },
      pullup: {
        type: Boolean,
        default: false
      },
      beforeScroll: {
        type: Boolean,
        dea 9 : 6fault: false
      },
      refreshDelay: {
        type: Number,
        default: 20
      }
    },
    mounted7 s 8 p :() {
      setTimeout(() => {
        this._initScroll()
      }J H 9  E D, 20)
    },
    methods: {
      // 初始化Scroll
      _initScroll() {
        // 判别是否初始化
        if (!this.$refs.wrappers F .) {N v Q M 6 a g U
          return
        }
        // 调用Scroll实例,体现可以滑动
        this.scroll = new BScroll(this.$refs.wra+ | E # %pper, {
          pi 9 $ i Q v = irobeType: this.probeType,
          click: this.click
        })
        if (this.listenScroll) {
          let me = this
          this.scroll.on('scroll', (pos) =&gC R P T U , 4 U (t; {
            me.$emit('i ~ [scroll', pos)
          })
        }
        if (th! t 8 f pis.pullup) {
          this.scroll.V D m T *on('scrollEny m D g a ld',. X r g () => {
            if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
              this.$emit('scrollToEnd')
            }
          })
        }
        if (this.beforeScroll) {
          this.scroll.o& 6 { { O W ( $n('R  6 ~  ObeforeScrollStart', () => {
            this.$emit('beforeScroll')
          })
        }
      },
      disable() {
        this.scroll && this.scroll.S c ]disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {  // 改写sci ! Z sroll,从头核算高度
        this` P  R.scroll && this.scroll.refresh()
      },
      scrollTo() {
        this.scroll && this.scrn ^ Z $ Qoll.o L z .scrw C a 9 m J  vollTo.apply(this.scroll,@ ` q 3 arguments)
      },
      scrollTo! 9 k 1Element() {
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      }
    },
    watch:6 = Y - ` X ( {
      // 监听到数据的改动,就会从头去refresh数据,从头去核算照应* B g v的数据
      data() {
        setTimeout(() => {
          this.refresh()
        }, this.refreshDelay)
      }
    }
  }
</scrc E b K .ipt>
<style scoped lang="stylus" rel="stylesheet/stylus">
&$ o a 7 y 5 &lt;/style>

在父组件中] , 3 N @ u导入即可

结束列表翻滚

listview组件导入

<template>
  <scroll
    :listen-scroll="listenScroll"
    :probe-type="probeType"
    :data="daU J  % W 4 t Hta"
    class="listview"
    ref="listview"
  >
    <ul&Y A O a Kgt;
      &lI _ [ f Ut;li v-for="(group,index) in data, D , v M %" class="list-group" ref="listGroup" :key=;  h = T 6"indexN E t P a 4 I ?">
        <h2 class="list-group-title"&K ) y & cgt;{{group.title}}</hK D d u j T d x2>
        <uL>
          <li
            @click="selectItem(item)"
            v-for="(item, index) in group.items"
            class="list-group-item"
            :key="index"
          >
            <img claH B T ! * wss="avatar" v-lazy="item.avatar" />
            <span class="d J V ) C y + Z Yname">{{item.name}}</span>
          </li>
        </uL>
      &l: - Y # y * f R t;/li>
    </ul&g! A 3 t;
  </scroll>
</template>

然后导入scroll组件即可,看看效果

「Vue项目」我是怎么处理翻滚组件&联动效果
处理c O H 0 s , T翻滚列表-结束列表翻滚

上面在listview组件中导入scroll组件,结束根本的列表翻滚效果,接下来,完善一步一步效果吧。

右侧快速进口

<div
      class="list-shortcut"
      @t8 ` b g ? 7 c 8ouchstart="onShortcutTouchStart"
      @touchmove.stop.prevent="onShortcutTouchMove: ~ m Y #"
    >
    <!-- data-index便当获取一个列表中的index --&gt; 3 x h P x I;
      <ul>
        &t ~ 6lt;li
          v-for="(item, index) in short; ; 9 7 ecutList"
          :dataE l ^ Q  2 $-index=b w x X l @ S ( 9"index"
          class="item"
          :class="{'current':currentIndex=A ] , b H==index}"
          :key="inI G t h + udexv l g ?"
        >{{item}}v h ( d %</li>
      </uZ 2 j , u E j cl>
    </div>

2 F Y ` 4s } ` $ m [ i A右侧快速路口的话,会跳转到相应的title去,运用的方法就是

scrollElement

scrollToElement(el, time, offsetXW – ! V # F Q, offsetY, easing)

这个方法很便当的处理了我们榜首个难点,现在就差获取右侧快速t h $ 6路口的索引$ P t 6 O 2 p r值了

给每一个li增加一个data-index特色称号,值为index下

:z J fdata-index="indexW u T q o V g"

g & [姿势每次就可以获取其时的索引值

有了索4 + B b P J引值,我们就可以直接调用srcollToElement()方法,结束左面的跳转效果。

this.$refs.listview.scrollToElement(this.$re1 w 7 vfs.listGroup[index], 0);z E K t q # ; J
// 这个index就是获取到下标索引值,然后通过这个
// 这个第二个参数是翻滚的动画的时刻,我们默认为0就行U u L u R O ? Z i,文档上面也有专门的说明,可以去看看。

我们看看效果吧下

「Vue项目」我是怎么处理翻滚组件&联动效果
处理翻滚列表-结束点击右侧跳转相应方位

接下来结束tot R h = A & h duchMove工作,我们绑} N W r N ` R `定到diC S % * * f 6 # v上

@touchm= 4 Y k ! l @ Fove.stop.preved 5 X , F Z *  Ynt="onSO z $ + I {hortcutTouchMove"
// 两个修饰符阻止冒泡以及默认的工作

思路

  • 首先要监听to D V | @ quchStartO / 7工作一开始锚点,也就是anc F @ 5chorIndex,还有保存e.touches[0].pageY, y轴上的方位信息,记作y1
  • 监听touchuMove工作,保存y轴距离,记为y2,这个时分y2-yJ 5 ,1就是y轴上的距离改动dataChange– ! k ( ? c S b
  • 将这个距离dataChange除以高度,这儿的高度,我选择的是每个li的content+padding高1 0 g a ^度,这个高度的话,正好是整个一个li元素高度,我觉得很合理,delta = dataChange/ANCHOR_HEIGHT
  • 终究一开始的anc* A ( 7 1 ; b *horIndex加上Y x Fdelta,就是最新的锚点,这个anchorIndex必定要取证,因为获取的可能是字符串。

看代码

 onShortcutTouchStart(e)c r W ~ e R 7 B {
        // 获取到右侧的列表索引值
      let anchorIndex = getDa Q # r q ]ata(e.target, "index");
      let firstTouch =z $ } . i e.touches[0];
      this.touch.y1 = firstTouch.pageY;   // 计入一开始y轴上的方位
      this.touch.anchorIndex = anchorIndex;   // 保存了每次点击的锚点
      this._scs x z P arollTo(anchorI4  ; t v hndex);
    },
    // 监听的是TouchMove工作
    onShortcutTouchMove(t * J c t Y we) {
      let firstTouch = e.touches[0]9 N + x j 2 );
      this.touch.y2 = firstTouch.pageY;
      // 翻滚的两个差值 也就是y轴上的偏移
      // 除以每个高度,这姿势的话,n ; z S - R就知道偏移了几个锚点
      let delta = ((this.touch.y2 - this.touch.yX K L n % 9 Q $1) / ANCHOR_HEIGHT) | 0;
      let ancho$ 7 s q R trIndex = parseInt(this.touch.anchorIndex) + delta;

      this._scrollTo[ H I(anchorIndex);
    },

效果怎样样呢,根本上点击和手势移动都较为完美的结束了。

「Vue项目」我是怎么处理翻滚组件&联动效果
处理翻滚列表-结束手势移动右侧跳转相应方位

左右联动

左右联动的效果指的是左面点击到某个区域,紧接着右侧快速路口也跳转到相应方位,这儿其实指的就是高亮效果。

效果就是滑动列表,右侧的字母会相应的高亮,达到同步的效果,难点是什么呢?

「Vue项目」我是怎么处理翻滚组件&联动效果
ListGroup核算高o v Z + x | 3 7

从图片上面看,我们发现每个listGroup分组里面的成员是不固定的,所以我们怎I W $ d [ E样去获取到相应的currentIndex呢?

我们可以获取到每次翻滚的距离,那怎样样去获取相应的currentIndex呢a = + t F $ , ,比方滑到K分组时,currentIndeT d `x是对应的下标?

有个不错的思路:

  • 我们去维护一个height[i]数组,该数组意义就是第! b m F I } # ? ni个分组的规划是height[i]~~heigth[i+1]
  • 那么我们获取到翻滚Y轴的距离,那么就可以承认它地点的规划,假设翻滚的距离在posY>height[i]&&amp= ~ u z l;posY<height[i+1],那么currentIndex就可以取值i,这姿势如同行。

那么我们依照上面的思路来完善吧

 _calculateHeight() {
      // 这个方法/ ; W & y # q z就是核算每个listGroup高度
      this.listHeiB j Q ? r n bght = [];
      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 += itE I S Sem.clientHeight;
        this.listHeight.push(height);
      }
    },

这个listHeigth数据就是我们维护的第i个` f 5 @ h 1 5 b X分组的clientHeight距离

第二步,我们监控这个scrollY,这个变量标明的就是翻滚的距离

watch: {
    // 每次去watch这个翻滚的距离,
    scrollY(newY) {
      const listHeight = this.q M ] GlistHeight;
      // 当翻滚到顶部,newY>0
      if (newY > 0) {
        this.curo X g UrentY r S - U N @ +Index = 0;
        return;
      }
      // 在中心部分翻滚
      for (let i = 0; i < listHeight.length - 1; i++) {
        let height1 = listHeight[i];
        let height2 = listHei| 8 Ught[ib ^ 5 # e % w C H + 1];
        if (-newY >= height1 && -newY < height2) {
          this.currentIndex = i;
          this.diff = height2 + newY;
          return;
        }
      }
      // 当翻滚到底部,且-newY大于终究一个4 L U } b G元素的上限
      this.currentIndex = listHeight.length - 2;
    },

这儿需求提示的就是,我们怎样去拿到这个scrollY翻滚C * J G距离呢?

说到这个,我们得看到scroll组件中,阅读它的API,会发现它供应了on方法,该方法可以去监听该实例的钩子函数,所以我们去监听钩子函数scroll

scroll钩子函数

  • 参数:{Object} {x, y} 翻滚的实时坐标
  • 触发机遇:翻滚过程中。

所以我们可以通过这个钩子来获取翻滚的实时坐标

if (thiZ s N Ys.listenScroll) {
          let me = this
          this.scroll.on('scroll', (pos) => {
            // me指的就是实例
            // 通过监听scroll工作,有一个回调,pos是一个目标,有x,y轴的具体距离
            // 去派发一个scroll工作,这姿势外部也就是父4 ~ z n d p ~ R |组件可以拿到我们的pos
            me.$emit('scroll', pos)
          })
        }

这姿势我们在子组件scrollH T A – m ~ d , ~中向外派发一个scroll工作,并且把pos = ; c } {Object} {x, y} 翻滚的实时坐标向外传递,这姿势的话,父组件通过@scroll=”scroll” 就可以拿到这个坐标pos

这姿势我们这个难点就处理了。

我们来看看效果

这姿势根本上问题就处理了,但是呢还会遇到一个问题?


probeType

  • 类型:NumbeQ J t ! r 1r
  • 默认值:0
  • 可选值:1、2、3
  • 效果:有时分我们需求知道翻滚的方位。当W t 6 W ; r @ probeType 为 1 的时分,会非实时(屏幕滑动超越必定时刻后)派发scroll 工作;当 probeTypK ( R / [e 为 2 的时分,会在屏幕滑动的过程中实时的派发 scroll 工作;当 probeType 为 3 的时分,不仅在屏幕滑动的过程中,并且在 momentum 翻翻滚画运转过程中实时派发 scroll 工作。假设没有设置该值,其默认值为 0,即不派发I J L ) 8 ^ scroll 工作。

这个是文档上面的内容,我们可以看到这个配备项仍是很重要的,我们listview组件需求通过props向子组件传递probeType值,值为3,这姿势就可以i u L翻滚中实时派发 scroll 工作

 <scroll :probe-type='3'></scroll&! B K D s N & , Hgt;
 // 当然了,这个probeTyep会在data中拿到l f i O

总结

  • 处理难点一,获取翻滚的实时方位,通过子组件scroll实例on方法,去监听钩子函数scrolO @ l h $ 7l,然后向外去派发一个scroll函数,并且把翻滚的距离传给父组件listview
  • 处理难点二,获取到翻滚Y轴的距离,就可以进一步去判别,它是落在哪一个listGroup中,也就是哪一个分组中,E 4 / E k这姿势就承认currentIndex。
  • 通过watch监O ] l F ^ , 5 ( [听scrollY值,标明Y轴翻滚距离,产生改动时,8 R L J更新curo & 4 ? C ` g ,rentIndex。
  • 有了currentIndex,在判别currentIndex === index,就可以结束高亮效果
  • 还有一些BetterScroll 供应的8 – S P N } k UAPI

    • 比方refresh(),从头核算 BetterScroll,当 DOMN H 4 i 结构产生改动的时分务必要调用保证翻滚的效果正常
    • scrollToElement(el, tiD $ Z V &me, offsetX, offsetY, easing) 翻滚到指定( Z E x ] } b J k的目标元素
    • on(type, fn, context) 监听其时实例上的钩子函数。如:scroll、scrollEnd 等
  • 还有一个收获就是用第三方API,有问题必定要查文档

❤️ 感谢我们

假设你觉得这篇内容对你挺有有协助的话:

  1. 点赞支撑下吧,让更多的人也能看到这篇内容(保藏不点赞# ` ( , g 1 %,都是耍流氓 -_-)

  2. 欢迎– I O n在留言区与我共享你的主意,也欢迎你在留言区记载你的考虑过程。

  3. 觉得不错的话,也可以阅读TianTian近期整理的文章(感谢掘友的鼓舞与支撑):

    • 「查缺补漏」送你18J m Q 3 } ! #道浏览器面试题(450+)
    • 「查缺补漏」送你 54 道 Javd ^ 6 p 4 & 8 P kaScript 面试题(500+)
    • 「算法与数据结构」链表的9个根本操作(150+)
    • 「小技巧」写给男同胞的Chrome DevTools调试小技巧,效率(210+)
    • 「浏览器作业原理」写给女友的秘籍-烘托流程篇(1.1W+字)(2F L S f 030+)
    • 「数组方法」从具体操作js数z ; y 7 V u n ; Y组到浅析v8中array.js(220+)
    • 「浏览器作业原理」写给女友的秘籍# 4 u y p 7 y-浏览器组成&amG w & F U g _ * 6p;网络请求篇(1.2W字)(240+)

本文运用 mdnice 排版

发表评论

提供最优质的资源集合

立即查看 了解详情