2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 使用better-scroll配合vue实现上拉加载下拉刷新组件

使用better-scroll配合vue实现上拉加载下拉刷新组件

时间:2024-05-15 11:32:31

相关推荐

使用better-scroll配合vue实现上拉加载下拉刷新组件

前提背景:大前端的时代造就了前端的业务逻辑越来越重,原有的MVP设计开发模式(jquery时代)造成了前端界面和数据操作都集中在MVP模式中的P层架构即控制器上,P层承担了大量的逻辑代码和操作视图DOM代码,两者交互在一起耦合性很强并且很重,而V(视图层)、M(数据层)的比重很轻,这对于业务功能越来越复杂的前端应用来说,大型的应用程序使用MVP模式已经不再是很好的选择。MVVM模式的诞生给我们构建复杂应用程序提供了极佳的管理模式和开发模式,VM层替代了原有的P层,它自动的处理dom和数据之间的关系,从而将我们把注意力集中在M层。随着MVVM越来越流行,遵循MVVM模式的Angular、React、Vue三大框架的应运而生,为我们开发大前端应用提供了极大的便利性。下面通过一小段代码对比MVP模式和MVVM之间的区别:

程序目标:实现简单的todolist效果:

MVP设计模式实现代码:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta name="viewport"

content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">

<title>jquery todolist</title>

<script src="../js/jquery-3.2.1.js"></script>

</head>

<body>

<div>

<input id="input" type="text">

<button id="btn">提交</button>

<ul id="list"></ul>

</div>

</body>

<script>

/*

* jquery开发遵循MVP模式,M模型 V视图 P控制器*/

// js构造函数

function Page() {

}

// 给Page对象实例增加一个方法init,调用bindEvents()方法,这个方法给button绑定一个点击事件

// $.proxy()方法将this.handleBtnClick()方法绑定到this即page实例

$.extend(Page.prototype, {

init: function () {

this.bindEvents();

},

bindEvents: function () {

var btn = $("#btn");

btn.on('click', $.proxy(this.handleBtnClick, this));

},

handleBtnClick: function () {

var inputElem = $("#input");

var inputVal = inputElem.val();

var ulElem = $("#list");

if(!inputVal){

window.alert('你输入的内容为空,不允许添加!');

return;

}

ulElem.append('<li>' + inputVal + '</li>');

inputElem.val('');

}

});

// 实例化对象调用方法

var page = new Page();

page.init();

</script>

</html>

MVP模式写的代码可以看出大量的逻辑代码和操作dom的代码交织在P控制层里,使用jquery开发遵循MVP模式的项目里,60%-70%的代码都和dom操作有关,造成了前端代码很重并且容易出错。

MVVM模式实现同样效果,使用vue框架实现:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta name="viewport"

content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">

<title>vue 实现todolist</title>

<script src="../js/vue.js"></script>

</head>

<body>

<div id="app">

<input v-model="inputVal" type="text">

<button v-on:click="handleBtnClick">提交</button>

<ul>

<li v-for="(item,index) in list" :key="index">{{item}}</li>

</ul>

</div>

</body>

<script>

var app = new Vue({

el: "#app",

data: {

inputVal: '',

list: []

},

methods: {

handleBtnClick: function () {

this.list.push(this.inputVal);

this.inputVal = '';

}

}

});

</script>

</html>

MVVM模式包含视图层(V)、数据层(M)、VM(层),不在包含控制器P层,而VM层是VUE框架内部实现的,我们不需要关心,通过上述实例可以看出我们进行的都是对数据的处理,数据发生改变,dom结构随之发生变化,VM层就是实现这一过程的机制。

再来引入一个思想:前端组件化思想,前端页面的每个可视区域都可以看做是一个个组件构成的,组件化的好处是当我们的前端界面功能复杂时,我们可以拆分成一个个组件,然后在每个组件里写相应的dom结构,js,css等,通过webpack等工程化工具使我们的组件组合成一个大界面,方便我们以后的维护和代码的复用,而我们所要做的上拉刷新下拉加载组件就是基于这一思想和vue的模式来实现的。

先来认识一下better-scrol,该项目的github地址是,点击打开链接

插件作者是黄轶,引用作者的一篇文章当better-scroll遇见vue,里面详细解释了better-scroll的滚动原理和上拉加载,下拉刷新所需的API,

废话不多说,直接贴源码,亲测可以完美正常使用!

scroll.vue

<template>

<div class="scroll-wrapper" ref="wrapper">

<div class="scroll-main">

<!-- 顶部提示信息 -->

<div class="top-tip">

<div class="refresh-top"><span v-show="IsRefresh" class="icon-jiazai"></span><span

class="refresh-hook">下拉刷新</span></div>

</div>

<slot></slot>

<div class="bottom-tip">

<span v-show="IsLoading" class="icon-jiazai"></span><span class="loading-hook">查看更多</span>

</div>

</div>

<div class="refresh-success alert-hook">刷新成功</div>

</div>

</template>

<script type="text/ecmascript-6">

import BScroll from 'better-scroll';

export default {

props: {

/**

* 1 滚动的时候会派发scroll事件,会截流。

* 2 滚动的时候实时派发scroll事件,不会截流。

* 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件

*/

probeType: {

type: Number,

default: 1

},

/**

* 点击列表是否派发click事件

*/

click: {

type: Boolean,

default: true

},

/**

* 是否开启横向滚动

*/

scrollX: {

type: Boolean,

default: false

},

/**

* 是否派发滚动事件

*/

listenScroll: {

type: Boolean,

default: false

},

/**

* 列表的数据

*/

data: {

type: Array,

default: null

},

/**

* 标识列表的数据是否为空

*/

flag: {

type: Boolean,

default: false

},

/**

* 是否派发滚动到底部的事件,用于上拉加载

*/

pullup: {

type: Boolean,

default: false

},

/**

* 是否派发顶部下拉的事件,用于下拉刷新

*/

pulldown: {

type: Boolean,

default: false

},

/**

* 是否派发列表滚动开始的事件

*/

beforeScroll: {

type: Boolean,

default: false

},

/**

* 当数据更新后,刷新scroll的延时。

*/

refreshDelay: {

type: Number,

default: 20

}

},

data() {

return {

IsRefresh: false,

IsLoading: false

};

},

mounted() {

// 保证在DOM渲染完毕后初始化better-scroll

setTimeout(() => {

this._initScroll();

}, 20);

},

methods: {

_initScroll() {

if (!this.$refs.wrapper) {

return;

}

// better-scroll的初始化

this.scroll = new BScroll(this.$refs.wrapper, {

probeType: this.probeType,

click: this.click,

scrollX: this.scrollX

});

// 是否派发滚动事件

if (this.listenScroll) {

this.scroll.on('scroll', (pos) => {

this.$emit('scroll', pos);

});

}

// 是否派发滚动到底部事件,用于上拉加载

if (this.pullup) {

this.scroll.on('scrollEnd', () => {

// 滚动到底部

if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {

this.$emit('scrollToEnd');

}

});

}

// 是否派发顶部下拉事件,用于下拉刷新

if (this.pulldown) {

this.scroll.on('touchEnd', (pos) => {

// 下拉动作

if (pos.y > 50) {

this.$emit('pulldown');

}

});

}

// 是否派发列表滚动开始的事件

if (this.beforeScroll) {

this.scroll.on('beforeScrollStart', () => {

this.$emit('beforeScroll');

});

}

},

disable() {

// 代理better-scroll的disable方法

this.scroll && this.scroll.disable();

},

enable() {

// 代理better-scroll的enable方法

this.scroll && this.scroll.enable();

},

refresh() {

// 代理better-scroll的refresh方法

this.scroll && this.scroll.refresh();

},

scrollTo() {

// 代理better-scroll的scrollTo方法

this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments);

},

scrollToElement() {

// 代理better-scroll的scrollToElement方法

this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments);

},

refreshTip() {

let topTip = document.querySelector('.refresh-hook');

topTip.innerText = '释放立即刷新';

this.IsRefresh = true;

},

refreshAlert(text) {

let alert = document.querySelector('.alert-hook');

text = text || '操作成功';

alert.innerHtml = text;

alert.style.display = 'block';

setTimeout(function () {

alert.style.display = 'none';

}, 1000);

},

loadData() {

let topTip = document.querySelector('.refresh-hook');

topTip.innerText = '下拉刷新';

this.IsRefresh = false;

this.refreshAlert('刷新成功');

},

shanglajiazai() {

let bottomTip = document.querySelector('.loading-hook');

bottomTip.innerText = '加载中...';

this.IsLoading = true;

setTimeout(() => {

if (!this.flag) {

bottomTip.innerText = '查看更多';

this.$emit('getData');

} else {

bottomTip.innerText = '没有更多数据';

}

this.IsLoading = false;

}, 1000);

}

},

watch: {

// 监听数据的变化,延时refreshDelay时间后调用refresh方法重新计算,保证滚动效果正常

data() {

setTimeout(() => {

this.refresh();

}, this.refreshDelay);

}

}

};

</script>

<style lang="stylus" rel="stylesheet/stylus">

@import "../../common/stylus/mixin.styl";

.scroll-wrapper

.scroll-main

.top-tip

.refresh-top

position: absolute

top: -40px

left: 0

refreshstyle(40px)

.refresh-hook

vertical-align: middle

.bottom-tip

refreshstyle(40px)

.loading-hook

vertical-align: middle

.refresh-success

display: none

position: fixed

top: 0

left: 0

z-index: 2

width: 100%

height: 40px

line-height: 40px

text-align: center

color: #fff

font-size: 12px

background: rgba(7, 17, 27, 0.5)

</style>

refreshstyle(40px)函数是使用stylus编写的复用样式函数,代码如下:

// 上拉刷新下拉加载样式函数

refreshstyle($top)

width: 100%

height: $top

z-index: 1

line-height: $top

text-align: center

color: #777

background: #f2f2f2

.icon-jiazai

display: inline-block

width: 15px

height: 15px

margin-right: 10px

vertical-align: middle

background-size: 15px 15px

background-repeat: no-repeat

background-image: url("jiazai.gif")

如何使用scroll.vue组件实现上拉加载,下拉刷新

<template>

<div>

<div class="header">测试上拉刷新下拉加载组件</div>

<scroll class="wrapper" ref="scrollContent"

:data="lastdata"

:pulldown="pulldown"

:pullup="pullup"

:flag="flag"

:listenScroll="listenScroll"

@pulldown="loadData"

@scroll="refreshTip"

@getData="getData"

@scrollToEnd="shanglajiazai">

<ul class="icon-wrapper">

<li class="icon-list border-1px" v-for="(item ,index) in lastdata" :key="index">

<i class="iconfont" :class="item.icon"></i>

<span class="title">{{item.name}}</span>

</li>

</ul>

</scroll>

</div>

</template>

<script type="text/ecmascript-6">

import scroll from 'components/scroll/scroll';

import { mockTableData } from 'common/js/page';

//mockTableData 函数是page.js中的一个处理数据的通用函数,这为前端模拟后台数据所写,不用care,请用实际上发出的promise请求从后端请求数据,明白原理即可。

export default {

data() {

return {

lastdata: this._initData(),

flag: false,

count: 1, // 页码

pagesize: 20, // 每页笔数

listenScroll: true,

pulldown: true,

pullup: true

};

},

created() {

this.refreshData();

},

methods: {

loadData() {

this.refreshData();

this.$refs.scrollContent.loadData();

},

refreshTip(pos) {

if (pos.y > 40) {

this.$refs.scrollContent.refreshTip();

}

},

shanglajiazai() {

this.$refs.scrollContent.shanglajiazai();

},

getData() {

this.count = this.count + 1;

let tempData = this._initData();

if (!tempData.length) {

this.flag = true;

} else {

this.lastdata = this.lastdata.concat(this._initData());

}

},

refreshData() {

this.count = 1;

this.lastdata = this._initData();

this.flag = false;

},

_initData() {

let icondata = [

{

icon: 'icon-sousuo',

name: 'icon-sousuo'

},

{

icon: 'icon-dianpu',

name: 'icon-dianpu'

},

{

icon: 'icon-huiyuanqia',

name: 'icon-huiyuanqia'

},

{

icon: 'icon-xiugaioryijian',

name: 'icon-xiugaioryijian'

},

{

icon: 'icon-shoucangxuanzhong',

name: 'icon-shoucangxuanzhong'

},

{

icon: 'icon-shoucang',

name: 'icon-shoucang'

},

{

icon: 'icon-quanbudingdan',

name: 'icon-quanbudingdan'

},

{

icon: 'icon-dianzan',

name: 'icon-dianzan'

},

{

icon: 'icon-app',

name: 'icon-app'

},

{

icon: 'icon-browser',

name: 'icon-browser'

},

{

icon: 'icon-form',

name: 'icon-form'

},

{

icon: 'icon-cart',

name: 'icon-cart'

},

{

icon: 'icon-home',

name: 'icon-home'

},

{

icon: 'icon-mine',

name: 'icon-mine'

},

{

icon: 'icon-31daifahuo',

name: 'icon-31daifahuo'

},

{

icon: 'icon-31daifukuan',

name: 'icon-31daifukuan'

},

{

icon: 'icon-31daishouhuo',

name: 'icon-31daishouhuo'

},

{

icon: 'icon-31daipingjia',

name: 'icon-31daipingjia'

},

{

icon: 'icon-tuikuantuihuo',

name: 'icon-tuikuantuihuo'

},

{

icon: 'icon-zhongtumoshi',

name: 'icon-zhongtumoshi'

},

{

icon: 'icon-xuanzekuangmoren',

name: 'icon-xuanzekuangmoren'

},

{

icon: 'icon-shanchu',

name: 'icon-shanchu'

},

{

icon: 'icon-gouwuchetianjia',

name: 'icon-gouwuchetianjia'

},

{

icon: 'icon-shouhuodizhi',

name: 'icon-shouhuodizhi'

},

{

icon: 'icon-wodefankui',

name: 'icon-wodefankui'

},

{

icon: 'icon-yijianfankui',

name: 'icon-yijianfankui'

},

{

icon: 'icon-shanchu3',

name: 'icon-shanchu3'

},

{

icon: 'icon-bianji',

name: 'icon-bianji'

},

{

icon: 'icon-huishouzhan7',

name: 'icon-huishouzhan7'

},

{

icon: 'icon-fenlei-copy',

name: 'icon-fenlei-copy'

},

{

icon: 'icon-chenggong',

name: 'icon-chenggong'

},

{

icon: 'icon-xuanzekuangxuanzhong',

name: 'icon-xuanzekuangxuanzhong'

},

{

icon: 'icon-bangzhuguanyuwomen',

name: 'icon-bangzhuguanyuwomen'

},

{

icon: 'icon-cancel-1-copy',

name: 'icon-cancel-1-copy'

},

{

icon: 'icon-chenggong1',

name: 'icon-chenggong1'

},

{

icon: 'icon-wodedaifahuo3dtouchshangpinxiangqing',

name: 'icon-wodedaifahuo3dtouchshangpinxiangqing'

},

{

icon: 'icon-lajitong',

name: 'icon-lajitong'

},

{

icon: 'icon-shangpin',

name: 'icon-shangpin'

},

{

icon: 'icon-zhifu',

name: 'icon-zhifu'

},

{

icon: 'icon-gouwu',

name: 'icon-gouwu'

},

{

icon: 'icon-faxian',

name: 'icon-faxian'

},

{

icon: 'icon-vivo',

name: 'icon-vivo'

},

{

icon: 'icon-discover',

name: 'icon-discover'

},

{

icon: 'icon-shouye1',

name: 'icon-shouye1'

},

{

icon: 'icon-yunshuzhongwuliu-xianxing',

name: 'icon-yunshuzhongwuliu-xianxing'

},

{

icon: 'icon-baoguofahuo-xianxing',

name: 'icon-baoguofahuo-xianxing'

},

{

icon: 'icon-shezhi-xianxing',

name: 'icon-shezhi-xianxing'

},

{

icon: 'icon-shouquan',

name: 'icon-shouquan'

},

{

icon: 'icon-dianzan1',

name: 'icon-dianzan1'

},

{

icon: 'icon-share_icon',

name: 'icon-share_icon'

},

{

icon: 'icon-dongtaixuanzhong',

name: 'icon-dongtaixuanzhong'

},

{

icon: 'icon-tijiao',

name: 'icon-tijiao'

},

{

icon: 'icon-VIVO',

name: 'icon-VIVO'

},

{

icon: 'icon-youjiantou',

name: 'icon-youjiantou'

},

{

icon: 'icon-zuojiantou',

name: 'icon-zuojiantou'

},

{

icon: 'icon-gouwuche',

name: 'icon-gouwuche'

},

{

icon: 'icon-dingdan',

name: 'icon-dingdan'

},

{

icon: 'icon-wode',

name: 'icon-wode'

},

{

icon: 'icon-shanchu1',

name: 'icon-shanchu1'

},

{

icon: 'icon-qijiandian',

name: 'icon-qijiandian'

},

{

icon: 'icon-shanchu2',

name: 'icon-shanchu2'

},

{

icon: 'icon-shouye2',

name: 'icon-shouye2'

},

{

icon: 'icon-shouye',

name: 'icon-shouye'

},

{

icon: 'icon-wo',

name: 'icon-wo'

},

{

icon: 'icon-shangchao',

name: 'icon-shangchao'

},

{

icon: 'icon-changyonggoupiaorenshanchu',

name: 'icon-changyonggoupiaorenshanchu'

},

{

icon: 'icon-praise',

name: 'icon-praise'

},

{

icon: 'icon-dongtai',

name: 'icon-dongtai'

},

{

icon: 'icon-shouye_xuanzhong',

name: 'icon-shouye_xuanzhong'

},

{

icon: 'icon-wode-',

name: 'icon-wode-'

},

{

icon: 'icon-gouwuche-xuanzhong',

name: 'icon-gouwuche-xuanzhong'

},

{

icon: 'icon-icon--',

name: 'icon-icon--'

},

{

icon: 'icon-fenleixuanzhong',

name: 'icon-fenleixuanzhong'

},

{

icon: 'icon-tubiaolunkuo-',

name: 'icon-tubiaolunkuo-'

},

{

icon: 'icon-tubiaolunkuo-1',

name: 'icon-tubiaolunkuo-1'

},

{

icon: 'icon-fenlei3',

name: 'icon-fenlei3'

},

{

icon: 'icon-dingdanquxiao',

name: 'icon-dingdanquxiao'

},

{

icon: 'icon-shoucangxuanzhong1',

name: 'icon-shoucangxuanzhong1'

},

{

icon: 'icon-zy_peisong',

name: 'icon-zy_peisong'

}

];

return mockTableData(this.count, this.pagesize, icondata);

}

},

components: {

scroll

}

};

</script>

<style lang="stylus" rel="stylesheet/stylus" scoped>

@import "../../common/stylus/mixin.styl";

.header

width: 100%

height: 40px

line-height: 40px

text-align: center

font-size: 14px

color: rgb(147, 153, 159)

.wrapper

position: absolute

top: 40px

bottom: 55px

width: 100%

overflow: hidden

.icon-wrapper

.icon-list

width: 100%

padding: 12px 6px

white-space: nowrap

overflow: hidden

text-overflow: ellipsis

font-size: 0

border-1px(rgba(7, 17, 27, 0.1))

.iconfont, .title

display: inline-block

vertical-align: top

line-height: 24px

.iconfont

font-size: 24px

color: mediumvioletred

.title

margin-left: 24px

font-size: 16px

color: dodgerblue

</style>

项目的实现效果如下:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。