标签:main 开始 code 样式 more mp4 dash into ops
web项目,聊天记录历史搜索。需要支持上拉无限加载,下拉无限加载。
支持所需场景;可配置。
顶部无限滚动很麻烦,网上没找着好的解决方案。刚开始 顶部也想使用 IntersectionObserver 特性来做,但二次触发比较麻烦,后来改用监听 scroll 事件。问题又来了,滚动条一直处于顶部,无法保持原来的位置。
本例解决方案是:利用 scrollIntoViewIfNeeded 特性,在组装列表完成后,手动调用,使其滚动到 原来的列表项位置。
凑合能用,O(∩_∩)O哈哈~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="//unpkg.com/vue/dist/vue.js"></script> <!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script> <style> body, html { margin: 0; padding: 0; height: 100%; } .scroll-container { height: 200px; overflow-y: scroll; background: red; } .scroll-container li { height: 50px; } </style> </head> <body> <div id="app"> <template> <tf-scroll ref="scrollHandle" list-item-dom-handle="li" :is-scroll-to-bottom="scrollOptions.isScrollToBottom" :top-observer-visible="scrollOptions.topObserverVisible" :bottom-observer-visible="scrollOptions.bottomObserverVisible" loading-text="加载呢" no-more-text="麻溜停,没了" @load-more-up="loadMoreUp" @load-more-down="loadMoreDown"> <ul> <li :class="{‘is-top‘: index == 3}" class="list-item" v-for="(item,index) in tableData">{{item.userName}}</li> </ul> </tf-scroll> </template> </div> <script> var Main = { data() { return { scrollOptions: { isScrollToBottom: false, topObserverVisible: true, bottomObserverVisible: true, }, addCountUp: 0, addCountDown: 0, tableData: [] } }, components: { /** * 上拉、下拉无限滚动组件 * 支持场景: * 1、滚动至底部无限加载 * 2、滚动至顶部无限加载 * 3、1与2场景共存 * * 配置项: * 1、is-scroll-to-bottom {boolean=false} 是否需要滚动到底部 * 2、top-observer-visible {boolean=false} 是否启用底部无限加载功能 * 3、bottom-observer-visible {boolean=true} 是否启用顶部无限加载功能 * 4、loading-text {string=拼命加载中} 数据加载中文本 * 5、no-more-text {string=没有更多数据} 没有更多文本 * 6、list-item-dom-handle {string} 列表项元素选择符;top-observer-visible 为真时,需要提供 * 7、load-more-up {event} 顶部无限加载广播事件名 * 8、load-more-down {event} 底部无限加载广播事件名 * * 使用过程注意事项: * 1、初始化: 父组件在 mounted 函数里,进行列表获取后,调用当前组件的init事件。 * 2、启用底部无限加载功能时:如果列表没有更多时,需要手动调用组件的 endObserveDown 事件。 * 3、启用顶部无限加载功能时: * a、如果列表没有更多时,需要手动调用组件的 endObserveUp 事件。 * b、每次获取数据成功后,需要调用组件的 endLoadingUp 事件,用以结束loading状态。 * */ tfScroll: { template: ` <div ref="scrollHandle" @scroll.native="intersectedUp" class="scroll-container"> <div style="display: flex; align-items: center; justify-content: center; height: 60px;" v-if="topObserverVisible">{{statusText}}</div> <slot></slot> <sentinels-observer ref="downSentinelsHandle" v-if="bottomObserverVisible" :loading-text="loadingText" :no-more-text="noMoreText" @intersect="intersectedDown"></sentinels-observer> </div> `, components: { sentinelsObserver: { template: ` <div style="display: flex; align-items: center; justify-content: center; height: 60px;">{{statusText}}</div> `, props: { options: Object, loadingText: { default: ‘拼命加载中‘, type: String }, noMoreText: { default: ‘没有更多数据‘, type: String } }, data: () => ({statusText: ‘‘, observer: null}), mounted() { this.statusText = this.loadingText }, methods: { startObserve () { const options = this.options || {}; this.observer = new IntersectionObserver(([entry]) => { if (entry && entry.isIntersecting) { this.$emit(‘intersect‘); } }, options); this.observer.observe(this.$el); }, removeObserve () { this.statusText = this.noMoreText this.observer.disconnect(); } }, destroyed() { this.observer.disconnect(); } } }, props: { listItemDomHandle: { type: String }, isScrollToBottom: { default: false, type: Boolean }, topObserverVisible: { default: false, type: Boolean }, bottomObserverVisible: { default: true, type: Boolean }, loadingText: { default: ‘拼命加载中‘, type: String }, noMoreText: { default: ‘没有更多数据‘, type: String }, }, data() { return { topLoading: false, topNoMore: false, } }, created () { this.scrollEvent = _.debounce(this.intersectedUp, 1000); }, computed: { statusText () { if (this.topNoMore) return this.noMoreText return this.loadingText } }, methods: { /* 初始化:第一次读取数据后,手动调用; */ init () { this.topLoading = false; this.topNoMore = false; this.$nextTick(() => { if (this.isScrollToBottom) this.$refs.scrollHandle.scrollTo(0, this.$refs.scrollHandle.scrollHeight); if (this.topObserverVisible) { if (!this.listItemDomHandle) throw ‘当前模式支持下拉滚动,请提供列表项元素选择符‘; let doms = this.$refs.scrollHandle.querySelectorAll(this.listItemDomHandle); doms[Math.floor(doms.length/2)].scrollIntoViewIfNeeded(); this.$refs.scrollHandle.addEventListener(‘scroll‘, this.scrollEvent) } if (this.bottomObserverVisible) this.$refs.downSentinelsHandle.startObserve(); }) }, intersectedUp () { // 顶部滚动事件:60 为 顶部观察器的元素高度;手动指定的滚动距离需要大于 当前元素的已滚动高度; if (this.$refs.scrollHandle.scrollTop <= 60*2 && !this.topNoMore) { this.$refs.scrollHandle.scrollTo(0, 60*3) if (!this.topLoading) { console.log(‘上方-滚动‘) this.topLoading = true; this.$emit(‘load-more-up‘); // 父组件 在获取成功后 需要将topLoading 置为 false } } }, intersectedDown() { console.log(‘下方-观察器出现,注意隐蔽‘) this.$emit(‘load-more-down‘); }, endLoadingUp (res) { this.$nextTick(() => this.$refs.scrollHandle.querySelectorAll(this.listItemDomHandle)[res.length].scrollIntoViewIfNeeded()); this.topLoading = false; }, endObserveUp () { this.topNoMore = true; this.topLoading = false; }, endObserveDown () { this.$refs.downSentinelsHandle.removeObserve() } } } }, mounted () { this.tableData = [ { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1575356523532", "messageType": ‘0‘, "body": {"msg": "恩"}, "userName": "0郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘1‘, "body": { "name": "图片发送于2015-05-07 13:59", //图片name "md5": "9894907e4ad9de4678091277509361f7", //图片文件md5 "url": "http://imgwx5.2345.com/dypcimg/drama/img/role/0/53208/xiaotingsheng.jpg", //生成的url "ext": "jpg", //图片后缀 "w": 6814, //宽 "h": 2332, //高 "size": 388245 //图片大小 }, "userName": "1郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘3‘, "body": { "dur": 8003, //视频持续时长ms "md5": "da2cef3e5663ee9c3547ef5d127f7e3e", //md5 "url": "http://nimtest.nos.netease.com/21f34447-e9ac-4871-91ad-d9f03af20412", //生成的url "w": 360, //宽 "h": 480, //高 "ext": "mp4", //视频格式 "size": 16420 //视频文件大小 }, "userName": "2郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘6‘, "body": { "name": "BlizzardReg.ttf", //文件名 "md5": "79d62a35fa3d34c367b20c66afc2a500", //文件MD5 "url": "http://nimtest.nos.netease.com/08c9859d-183f-4daa-9904-d6cacb51c95b", //文件URL "ext": "ttf", //文件后缀类型 "size": 91680 //大小 }, "userName": "3郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘9‘, "body": "{\"msg\":\"恩\"}", "userName": "4郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘1‘, "body": { "name": "图片发送于2015-05-07 13:59", //图片name "md5": "9894907e4ad9de4678091277509361f7", //图片文件md5 "url": "http://imgwx3.2345.com/dypcimg/drama/img/role/0/53208/linxi.jpg", //生成的url "ext": "jpg", //图片后缀 "w": 6814, //宽 "h": 2332, //高 "size": 388245 //图片大小 }, "userName": "5郭劭杰", "businessRowNO": 1 }, { "favicon": "686481", "OID": "a39a6dbdb954442dac9f6b5dd2791f16", "sendtime": "1555659541837", "messageType": ‘3‘, "body": { "dur": 8003, //视频持续时长ms "md5": "da2cef3e5663ee9c3547ef5d127f7e3e", //md5 "url": "https://www.w3school.com.cn/i/movie.ogg", //生成的url "w": 360, //宽 "h": 480, //高 "ext": "mp4", //视频格式 "size": 16420 //视频文件大小 }, "userName": "6郭劭杰", "businessRowNO": 1 }, ]; this.$refs.scrollHandle.init(); }, methods: { loadMoreUp () { this.loadData(true).then(res => { res.forEach(item => this.tableData.unshift(item)); this.$refs.scrollHandle.endLoadingUp(res) }).catch(() => { this.$refs.scrollHandle.endObserveUp() }); }, loadMoreDown () { this.loadData().then(res => { res.forEach(item => this.tableData.push(item)) }).catch(() => this.$refs.scrollHandle.endObserveDown()); }, loadData (isUp) { let namePre = ‘‘; if (isUp) { this.addCountUp ++; namePre = `up${this.addCountUp}` } else { this.addCountDown ++; namePre = `down${this.addCountDown}` } if (isUp && this.addCountUp > 2) return Promise.reject(); else if (this.addCountDown > 2) return Promise.reject(); return this.mockData([ {userName: `${namePre}-0`}, {userName: `${namePre}-1`}, {userName: `${namePre}-2`}, {userName: `${namePre}-3`}, {userName: `${namePre}-4`}, {userName: `${namePre}-5`}, {userName: `${namePre}-6`}, {userName: `${namePre}-7`}, {userName: `${namePre}-8`}, ]) }, mockData (data) { return new Promise((resolve, reject) => { setTimeout(function () { resolve(data) }, 500) }); } } } var Ctor = Vue.extend(Main) new Ctor().$mount(‘#app‘) </script> </body> </html>
标签:main 开始 code 样式 more mp4 dash into ops
原文地址:https://www.cnblogs.com/fan-zha/p/11988459.html