前端页面如何实现下拉刷新
- 2019 年 10 月 12 日
- 筆記
页面的滚动区域本来是用的插件vue-scroller,但是由于在一些低端安卓机中页面略有卡动,可能是GPU不给力造成的,这里尝试用原生的overflow: auto;
来测试一下滚动效果,发现效果不错,但是在ios设备中就发现了了滚动没有惯性很死板的体验,这里可以在滚动容器中写一行css属性-webkit-overflow-scrolling: touch;
,这样就有原生的滚动体验啦,接下来就差一个下拉刷新的效果了,没有找到现成的轮子,这里就自己开撸
如何实现
- 当容器的scrollTop为0的时候,使用transform: translateY来模拟
- 检测下拉的高度当达到某一固定值的时候,释放手指,调用回调函数实现下拉刷新
着手实现
既然是下拉我们肯定需要监听touchstart
、touchmove
、touchend
三个dom事件
touchstart
el.addEventListener('touchstart', e => { if (el.scrollTop !== 0) { return } beginPagY = e.touches[0].pageY e.preventDefault() })
用于记录手指点按屏幕时候的位置,为了后续translateY的值计算做准备
touchstartmove
el.addEventListener('touchmove', e => { if (el.scrollTop !== 0) { return } const pageY = e.touches[0].pageY const distance = currentPos = pageY - beginPagY if (distance < 0 || distance > maxTranslateY) { // 上拉的和超过最大限定高度时候不做任何处理 return; } if (distance > 60) { iconEl.classList.add('active') } else { iconEl.classList.remove('active') } e.preventDefault() el.style.transform = `translateY(${distance}px)` })
touchmove主要是根据手指下拉的距离不断的修改container的translate的值来达到效果, 同时记录当前下拉的distance,用于松手是判断是否触发下拉刷新的效果; 同时在模拟下拉效果的时候要阻止系统默认事件e.preventDefault()
touchend
let clear = () => { this.isShowLoading = false el.style.transform = `translateY(0)` setTimeout(() => { el.style.transition = `` }, 200) } el.addEventListener('touchend', () => { el.style.transition = `.2s` if (currentPos >= 60) { this.isShowLoading = true el.style.transform = `translateY(30px)` callback && callback(() => { clear() }) return } clear() })
touchend 主要是用来松手时是否执行下拉刷新的效果, 当currentPos达到预先设定的值的时候就触发回调函数,这里设置transition来增加动画效果
最终效果
所有代码
<!doctype html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> * { margin: 0; padding: 0; } #app { height: 100vh; overflow: hidden; display: flex; flex-direction: column; } header { width: 100%; height: 44px; background: teal; z-index: 10; } main { height: calc(100% - 44px); overflow: auto; -webkit-overflow-scrolling: touch; flex: 1; } .item { font-size: 12px; line-height: 30px; color: #aaa; } .item + .item { border-top: 1px solid; } </style> <style> .css-icon { display: inline-block; height: 1em; width: 1em; font-size: 20px; box-sizing: border-box; text-indent: -9999px; vertical-align: middle; position: relative; } .css-icon::before, .css-icon::after { content: ''; box-sizing: inherit; position: absolute; left: 50%; top: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } .icon-upward::before { height: .65em; width: .65em; border-style: solid; border-width: 2px 0 0 2px; -ms-transform: translate(-50%, -50%) rotate(45deg); transform: translate(-50%, -50%) rotate(45deg); } .icon-upward::after { height: .8em; border-left: 2px solid; top: 55%; } .icon-upward.active { transform: rotate(180deg); transition: transform .3s; } .pull-to-refresh-layer { height: 60px; margin-top: -60px; font-size: 12px; text-align: center; color: #aaa; line-height: 30px; } </style> </head> <body> <div id="app"> <header></header> <main> <div ref="container"> <div class="pull-to-refresh-layer"> <div v-show="!isShowLoading"> <i ref="icon" class="css-icon icon-upward"></i> <p>下拉刷新</p> </div> <div v-show="isShowLoading" style="padding-top: 30px;"> 假装是个loading图标 </div> </div> <div class="item" v-for="item in list" :key="item">{{item}}</div> </div> </main> </div> <script src="./vue.js"></script> <script> new Vue({ el: '#app', data: { isShowLoading: false }, computed: { list() { return new Array(100).fill(0).map((item, index) => index) } }, mounted() { this.pullRefresh(this.$refs.container, (done) => { setTimeout(() => { done() }, 1000) }) }, methods: { pullRefresh(el, callback) { let beginPagY = 0 let currentPos const maxTranslateY = 150 const iconEl = this.$refs.icon el.addEventListener('touchstart', e => { if (el.scrollTop !== 0) { return } beginPagY = e.touches[0].pageY e.preventDefault() }) el.addEventListener('touchmove', e => { if (el.scrollTop !== 0) { return } const pageY = e.touches[0].pageY const distance = currentPos = pageY - beginPagY if (distance < 0 || distance > maxTranslateY) { // 上拉的时候不做任何处理 return; } if (distance > 60) { iconEl.classList.add('active') console.log(iconEl.classList); } else { iconEl.classList.remove('active') } e.preventDefault() el.style.transform = `translateY(${distance}px)` }) let clear = () => { this.isShowLoading = false el.style.transform = `translateY(0)` setTimeout(() => { el.style.transition = `` }, 200) } el.addEventListener('touchend', () => { el.style.transition = `.2s` if (currentPos >= 60) { this.isShowLoading = true el.style.transform = `translateY(30px)` callback && callback(() => { clear() }) return } clear() }) } } }) </script> </body> </html>