頁面的滾動區域本來是用的插件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>