前端頁面如何實現下拉刷新
- 2019 年 10 月 12 日
- 筆記
頁面的滾動區域本來是用的插件vue-scroller,但是由於在一些低端Android機中頁面略有卡動,可能是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>