用Vue来实现音乐播放器(10):Scroll组件的抽象和应用
了解better-scroll什么时候是需要refresh计算的??通常我们遇到的better-scroll不能滚动的问题的根源是什么??better-scroll的渲染原理是:根据初始化的时机 或者调用refresh()的时机的那个时候的scroll的父元素的高度和子元素的高度之差去做一个计算 计算出他可以滚动的位置 所以我们去实例化或者refresh这个scroll的时候 一定要保证 这个dom是已经渲染好的 我们能正确计算到高度的
如果我们有数据变化以及dom变化的场景的时候 我们一定要去让better-scroll重新refresh() 然后我们封装了scroll组件之后 我们就不用在外部掉获取到数据之后 再this.$refs.scroll.refresh() 我们只需要把数据传递给scroll scroll就能监听到数据变化 scroll组件就能自己计算高度
<template> <div class="recommend"> <scroll class="recommend-content"> <!-- //scroll插件作用于他的第一个子元素 --> <!-- 局部轮播的父级 --> <div> <div class="slider-wrapper" v-if="recommend.length"> //这里也是为了在初始化实例和refresh()之前确保dom已经渲染好了 <slider> <!-- 填写插槽 --> <div v-for="(item,index) in recommend" :key="index"> <a :href="item.linkUrl"> <img :src="item.picUrl"> </a> </div> </slider> </div> <div class="recommend-list"> <h1 class="list-title">热门歌单推荐</h1> <div class="mod-playlist"> <ul class="playlist-list"> <li v-for="(item,index) in discList" :key="index" class="playlist-item"> <div class="playlist-item-box"> <div class="playlist-cover mod-cover"> <a href="#"> <img :src="item.imgurl" class="pic"> </a> </div> <h4 class="playlist-title"> <span class="playlist-title-txt">{{item.dissname}}</span> </h4> <div class="playlist-author"> <a href="#">{{item.creator.name}}</a> </div> </div> </li> </ul> </div> </div> </div> </scroll> </div> </template> <script> import Scroll from '../../base/scroll/scroll.vue' import Slider from '../../base/slider/slider.vue' import {getRecommend,getDiscList} from '../../api/recommend.js' import { ERR_OK } from '../../api/config.js' export default { data(){//为了将实例里面的轮播图数据与dom相关 要有一个data()方法 return { recommend:[], discList:[] } }, created(){//一般在这个生命钩子里面加载数据 this._getRecommend(), this._getDiscList() }, methods:{ _getRecommend(){ getRecommend().then((res)=>{//这个res就是我们抓取到的数据对象 if(res.code===ERR_OK){ // console.log(res.data.slider) this.recommend=res.data.slider } }) }, _getDiscList(){ getDiscList().then((res)=>{ if(res.code===ERR_OK){ // console.log(res.data.list) this.discList=res.data.list } }) }, }, components:{ Slider,Scroll } }
<template> <div ref="wrapper"> <slot></slot> </div> </template> <script> //将better-scroll相关都放在这里 可以实现代码的复用 import BScroll from 'better-scroll' export default { props: { probeType: { type: Number, default: 1 }, click: { type: Boolean, default: true }, data: {//因为我们是滚动的 所以数据会改变 需要一个数组来监听发生的数据变化 从而可以refresh()scroll之后 重新计算滚动的高度 type:Array, default:null } }, //vue在mounted()这个生命周期钩子里面加载dom到页面上 一般在这个钩子里面还可以初始化插件 以及操作dom //要写个定时器,,因为一般数据的请求是异步的 那么异步的数据请求和同样也是异步的mounted()的顺序是什么呢? //可能是mounted()先执行 那么就会在数据更新之前将dom挂载了 这样会产生虚拟dom //所以我们要保证dom的挂载是在数据更新之后 所以使用setTimeout() 或者是this.$nextTick //可以用nextTick 将回调延迟到下次dom更新循环之后执行 在修改数据之后立即使用它,然后等待dom更新 //浏览器渲染一般是17ms 这里延迟到20ms mounted(){ setTimeout(()=>{ this._initScroll(); //better-scroll的渲染原理是 //根据初始化的时机 或者调用refresh()的时机的那个时候的scroll的父元素的高度和子元素的高度之差去做一个计算 //计算出他可以滚动的位置 所以我们去实例化或者refresh这个scroll的时候 一定要保证 这个dom是已经渲染好的 我们能正确计算到高度的 //所以我们在这里一定要保证我们能计算到正确的高度 因此我们要判断一下 此时dom有没有渲染好 在recommend.vue组件中确认 },20) }, methods:{ _initScroll(){ //在这里先判断一下this.$refs.wrapper是不是undefined //如果是undefined 那么BScroll类由于第一个参数为undefined会报错 if(!this.$refs.wrapper){ return } this.scroll=new BScroll(this.$refs.wrapper,{ probeType:this.probeType, click:this.click }) }, enable(){ this.scroll&&this.scroll.enable() }, disable(){ this.scroll&&this.scroll.disable() }, refresh(){ this.scroll&&this.scroll.refresh() } }, //监控data的变化 如果data变化了 刷新scroll watch:{ data(){ setTimeout(()=>{ //这里也是操作dom 只要涉及到操作dom就需要延迟 this.refresh() },20) } } } </script> <style lang="stylus" scoped> </style>
完成以上这些操作 scroll组件已经初始化了 但是并不能滚动 这是因为 我们初始化这个scroll插件的时机是在scroll.vue组件中的mounted()里面初始化到的 然而我的scroll插件里面的数据是动态渲染拿到的 也就是一个异步操作 即mounted()挂载的前提是 也可以说是实例化或者是refresh()这个插件的前提是 动态获取到数据之后 将dom撑开了之后 能够计算到滚动的高度差之后 在挂载到页面 然后此时我们重新计算scroll 然后调用this.refresh()
才能让scroll正确的滚动
那么我们在recommend.vue中就要确保一下有数据 或者是直接父传子一个data属性 如果recommend.vue中的数据变化了 就会触发scroll.vue中的高度改变
<template> <div class="recommend"> <!-- 这里的data可以看成是父传子的属性 即<scroll>组件中的props属性下的data 当这里的data改变了的话 scroll组件的data也会改变 因为监听(watch)了data 所以data改变之后就会刷新scroll 即重新计算滚动的高度等等 如果这里先没有传一个data过去 那么我们可能在初始化scroll组件里面的时候可能拿不到数据 如果没有数据 那么scroll组件可能认为不需要滚动 --> <scroll class="recommend-content" :data="discList" ref="scroll"> <!-- //scroll插件作用于他的第一个子元素 --> <!-- 局部轮播的父级 --> <div> <!-- 注意这里要写当轮播图的数据都拿到了之后 即slider.vue组件里面的mounted()方法能执行成功之后 执行下面操作 --> <!-- 因为我们的recommend是通过异步拿到的 可能会有延时 但是如果不加上v-if的话 我们可能在没有拿到数据的情况下就 把插槽插入进去了 那么slider.vue里面的mounted()也就执行了 --> <div class="slider-wrapper" v-if="recommend.length"> <slider> <!-- 填写插槽 --> <div v-for="(item,index) in recommend" :key="index"> <a :href="item.linkUrl"> <img :src="item.picUrl" @load="loadImage" class="needsclick"> </a> </div> </slider> </div> <div class="recommend-list"> <h1 class="list-title">热门歌单推荐</h1> <div class="mod-playlist"> <ul class="playlist-list"> <li v-for="(item,index) in discList" :key="index" class="playlist-item"> <div class="playlist-item-box"> <div class="playlist-cover mod-cover"> <a href="#"> <img v-lazy="item.imgurl" class="pic"> </a> </div> <h4 class="playlist-title"> <span class="playlist-title-txt">{{item.dissname}}</span> </h4> <div class="playlist-author"> <a href="#">{{item.creator.name}}</a> </div> </div> </li> </ul> </div> </div> </div> </scroll> </div> </template>