LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【HTML5】H5 下拉刷新如何實(shí)現(xiàn)

admin
2024年3月30日 0:12 本文熱度 786

本文轉(zhuǎn)載于稀土掘金技術(shù)社區(qū)——小霖家的混江龍

最近我需要做一個(gè)下拉刷新的功能,實(shí)現(xiàn)功能后我發(fā)現(xiàn),它需要處理的情況還蠻多,于是我整理了這篇文章。

下圖是我實(shí)現(xiàn)的效果,分為三步:開(kāi)始下拉時(shí),屏幕頂部會(huì)出現(xiàn)加載動(dòng)畫(huà);加載過(guò)程中,屏幕頂部高度保持不變;加載完成后,加載動(dòng)畫(huà)隱藏。

pull-down.gif

首先我會(huì)講解下拉的原理、根據(jù)原理寫(xiě)出初始代碼;然后我會(huì)說(shuō)明代碼存在的缺陷、解決缺陷并做些額外優(yōu)化;最后我會(huì)給出完整代碼,并做一個(gè)總結(jié)。

拳打 H5,腳踢小程序。我是「小霖家的混江龍」,關(guān)注我,帶你了解更多實(shí)用的前端武學(xué)。

下拉的原理

prinple.png

如圖所示,藍(lán)色框代表視口,綠色框代表容器,橙色框代表加載動(dòng)畫(huà)。最開(kāi)始時(shí),加載動(dòng)畫(huà)處于視口外;開(kāi)始下拉之后,容器向下移動(dòng),加載動(dòng)畫(huà)從上方進(jìn)入視口;結(jié)束下拉后,容器又開(kāi)始向上移動(dòng),加載動(dòng)畫(huà)也從上方退出視口。

下拉基礎(chǔ)代碼

知道原理,我們現(xiàn)在開(kāi)始寫(xiě)實(shí)現(xiàn)代碼,首先是布局的代碼:

布局代碼

我們把 box 元素當(dāng)作容器,把 loader-box,loader-box + loading 元素當(dāng)作動(dòng)畫(huà),至于 h1 元素不需要關(guān)注,我們只把它當(dāng)作操作提示。

<div id="box">
  <div class="loader-box">
    <div id="loading"></div>
  </div>
  <h1>下拉刷新 ↓</h1>
</div>

loader-box 的高度是 80px,按上一節(jié)原理中的分析,初始時(shí)我們需要讓 loader-box 位于視口上方,因此 CSS 代碼中我們需要把它的位置向上移動(dòng) 80px。

.loader-box {
  position: relative;
  top: -80px;
  height80px;
}

loader-box 中的 loader 是純 CSS 的加載動(dòng)畫(huà)。我們利用 border 畫(huà)出的一個(gè)圓形邊框,左、上、右邊框是淺灰色,下邊框是深灰色:

loader.png

#loader {
  width25px;
  height25px;
  border3px solid #ddd;
  border-radius50%;
  border-bottom3px solid #717171;
  transformrotate(0deg);
}

開(kāi)始刷新時(shí),我們給 loader 元素增加一個(gè)動(dòng)畫(huà),讓它從 0 度到 360 度無(wú)限旋轉(zhuǎn),就實(shí)現(xiàn)了加載動(dòng)畫(huà):

loading.gif

#loader.loading {
  animation: loading 1s linear infinite;
}

@keyframes loading {
  from { transformrotate(0deg); }
  to { transformrotate(360deg); }
}

邏輯代碼

看完布局代碼,我們?cè)倏催壿嫶a。邏輯代碼中,我們要監(jiān)聽(tīng)用戶(hù)的手指滑動(dòng)、實(shí)現(xiàn)下拉手勢(shì)。我們需要用到三個(gè)事件:

  • touchstart[1] 代表觸摸開(kāi)始;
  • touchmove[2] 代表觸摸移動(dòng);
  • touchend[3] 代表觸摸結(jié)束。

從 touchstart 和 touchmove 事件中我們可以獲取手指的坐標(biāo),比如 event.touches[0].clientX 是手指相對(duì)視口左邊緣的 X 坐標(biāo),event.touches[0].clientY 是手指相對(duì)視口上邊緣的 Y 坐標(biāo);從 touchend 事件中我們則無(wú)法獲得 clientX 和 clientY

我們可以先記錄用戶(hù)手指 touchstart 的 clientY 作為開(kāi)始坐標(biāo),記錄用戶(hù)最后一次觸發(fā) touchmove 的 clientY 作為結(jié)束坐標(biāo),二者相減就得到手指移動(dòng)的距離 distanceY。

設(shè)置手指移動(dòng)多少距離,容器就移動(dòng)多少距離,就得到了我們的邏輯代碼:

const box = document.getElementById('box')
const loader = document.getElementById('loader')
let startY = 0, endY = 0, distanceY = 0

function start(e{
  startY = e.touches[0].clientY
}

function move(e{
  endY =  e.touches[0].clientY
  distanceY = endY - startY
  box.style = `
    transform: translateY(${distanceY}px);
    transition: all 0.3s linear;
  `

}

function end({
  setTimeout(() => {
    box.style = `
      transform: translateY(0);
      transition: all 0.3s linear;
    `

    loader.className = 'loading'
  }, 1000)
}

box.addEventListener('touchstart', start)
box.addEventListener('touchmove', move)
box.addEventListener('touchend', end)

邏輯代碼實(shí)現(xiàn)一個(gè)簡(jiǎn)陋的下拉效果,當(dāng)然現(xiàn)在還有很多缺陷。

pull-down-basic.gif

簡(jiǎn)陋下拉效果的 6 個(gè)缺陷

之前我們實(shí)現(xiàn)了簡(jiǎn)陋的下拉效果,它還需要解決 6 個(gè)缺陷,才能算一個(gè)完善的功能。

沒(méi)有最小、最大距離限制

第一個(gè)缺陷是,下拉沒(méi)有做最小、最大距離的限制。

通常來(lái)說(shuō),我們下拉屏幕時(shí),距離太小應(yīng)該不能觸發(fā)刷新,距離太大也不行,下滑到一定距離后,就應(yīng)該無(wú)法繼續(xù)下滑。

因此我們可以給下拉設(shè)置最小距離限制 DISTANCE_Y_MIN_LIMIT、最大距離限制 DISTANCE_Y_MAX_LIMIT。如果 touchend 中發(fā)現(xiàn)下拉距離小于最小距離,直接不觸發(fā)加載;如果 touchmove 中下拉距離超過(guò)最大距離,頁(yè)面只向下移動(dòng)最大距離。

解決缺陷關(guān)鍵代碼如下:

const DISTANCE_Y_MAX_LIMIT = 150
  DISTANCE_Y_MIN_LIMIT = 80

function move(e{
  endY =  e.touches[0].clientY
  distanceY = endY - startY
  if (distanceY > DISTANCE_Y_LIMIT) {
    distanceY = DISTANCE_Y_LIMIT
  }
  box.style = `
    transform: translateY(${distanceY}px);
    transition: all 0.3s linear;
  `

}

function end({
  if (distanceY < DISTANCE_Y_MIN_LIMIT) {
    box.style = `
      transform: translateY(0px);
      transition: all 0.3s linear;
    `

    return
  }
  ...
}

加載動(dòng)畫(huà)沒(méi)有停留在視口頂部

第二個(gè)缺陷是,下拉沒(méi)有讓加載動(dòng)畫(huà)停留在視口頂部。

我們可以把 end 函數(shù)加以改造,在數(shù)據(jù)還沒(méi)有加載完成時(shí)(用 setTimeout 模擬的),讓加載動(dòng)畫(huà) style 的 translateY 一直是 80px,translateY(80px) 可以和 初始 CSS 的 top: -80px; 相互抵消,讓動(dòng)畫(huà)在未刷新完成前停留在視口頂部。

function end({
  ...
  box.style = `
    transform: translateY(80px);
    transition: all 0.3s linear;
  `

  loader.className = 'loading'
  setTimeout(() => {
    box.style = `
      transform: translateY(0px);
      transition: all 0.3s linear;
    `

    loader.className = ''
  }, 1000)
}

重復(fù)觸發(fā)

第三個(gè)缺陷是,下拉可以重復(fù)觸發(fā)。

正常來(lái)說(shuō),如果我們已經(jīng)下拉過(guò),數(shù)據(jù)正在加載中時(shí),我們不能繼續(xù)下拉。

我們可以增加一個(gè)加載鎖 loadLock。當(dāng)加載鎖開(kāi)啟時(shí),start,move 和 end 事件都不會(huì)觸發(fā)。

let loadLock = false

function start(e{
  if (loadLock) { return }
  ...
}

function move(e{
  if (loadLock) { return }
  ...
}

function end(e{
  if (loadLock) { return }
  ...
  setTimeout(() => {
    ...
    loadLock = true
    ...
  }, 1000)
}

沒(méi)有限制方向

第四個(gè)缺陷是,沒(méi)有限制方向。

目前我們的代碼,用戶(hù)上拉也能觸發(fā)。我們可以增加判斷,當(dāng) endY - startY 小于 0 時(shí),阻止 touchmove 和 touchend 的邏輯。

function move(e{
  ...
  if (endY - startY < 0) { return }
  ...
}

function end({
  if (endY - startY < 0) { return }
  ...
}

你可能會(huì)疑惑,為什么我寧愿寫(xiě)多個(gè)判斷攔截,也不取消監(jiān)聽(tīng)事件。這是因?yàn)?strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; background-attachment: scroll; background-clip: border-box; background-image: none; background-origin: padding-box; background-position: 0% 0%; background-repeat: no-repeat; background-size: auto; width: auto; height: auto; border-style: none; border-width: 3px; border-color: rgba(0, 0, 0, 0.4); border-radius: 0px;">一旦取消監(jiān)聽(tīng)事件,我們需要考慮在一個(gè)合適的時(shí)間重新監(jiān)聽(tīng),這會(huì)把問(wèn)題變得更復(fù)雜。

沒(méi)有阻止原生滾動(dòng)

第五個(gè)缺陷時(shí),我們?cè)诩虞d數(shù)據(jù)時(shí)沒(méi)有阻止原生滾動(dòng)。

雖然我們已經(jīng)阻止了重復(fù)下拉,touchmove 和 touchend 事件被攔截了,但是 H5 原生滾動(dòng)還能用。

我們可以在刷新時(shí)給 body 設(shè)置一個(gè) overflow: hidden; 屬性,刷新結(jié)束后清除 overflow: hidden,這樣就可以阻止原生滾動(dòng)。

body.overflowHidden {
  overflow: hidden;
}
const body = document.body
function end({
  ...
  box.style = `
    transform: translateY(80px);
    transition: all 0.3s linear;
  `

  loader.className = 'loading'
  body.className = 'overflowHidden'
  setTimeout(() => {
    ...
    box.style = `
      transform: translateY(0px);
      transition: all 0.3s linear;
    `

    loader.className = ''
    body.className = ''
  }, 1000)
}

沒(méi)有阻止 iOS 橡皮筋效果

第 6 個(gè)缺陷是,沒(méi)有阻止 iOS 的橡皮筋效果。

iOS 瀏覽器默認(rèn)滑動(dòng)時(shí)有一個(gè)橡皮筋效果,我們需要阻止它,避免影響我們的下拉手勢(shì)。阻止方式就是給監(jiān)聽(tīng)器設(shè)置 passive: false

function addTouchEvent({
  box.addEventListener('touchstart', start, { passivefalse })
  box.addEventListener('touchmove', move, { passivefalse })
  box.addEventListener('touchend', end, { passivefalse })
}

addTouchEvent()

解決完 6 個(gè)缺陷后,我們已經(jīng)得到無(wú)缺陷的下拉刷新功能,但離絲滑的下拉刷新還有一段距離。我們還可以做一些優(yōu)化,讓下拉刷新更完善。

優(yōu)化

我們可以做兩個(gè)優(yōu)化,第一個(gè)優(yōu)化是添加阻尼效果:

增加阻尼效果

所謂阻尼效果,就是下拉過(guò)程我們可以感受到一股阻力的存在,雖然我們下拉力度是一樣的,但距離的增加速度變慢了。用物理術(shù)語(yǔ)表示的話(huà),就是加速度變小了。

體現(xiàn)到代碼上,我們可以設(shè)置一個(gè)百分比,百分比會(huì)隨著下拉距離增加而減少,把百分比乘以距離當(dāng)作最后的距離。

代碼中百分比 percent 設(shè)為 (100 - distanceY * 0.5) / 100,當(dāng) distanceY 越來(lái)越大時(shí),百分比 percent 越來(lái)越小,最后再把 distanceY * percent 賦值給 distanceY

function move(e{
  ...
  distanceY = endY - startY
  let percent = (100 - distanceY * 0.5) / 100
  percent = Math.max(0.5, percent)
  distanceY = distanceY * percent
  if (distanceY > DISTANCE_Y_MAX_LIMIT) {
    distanceY = DISTANCE_Y_MAX_LIMIT
  }
  ...
}

利用角度判斷用戶(hù)下拉意圖

第二個(gè)優(yōu)化是利用角度判斷用戶(hù)下拉意圖。

下圖展示了兩種用戶(hù)下拉的情況,β 角度比 α 角度小,角度越小用戶(hù)下拉意圖越明顯、誤觸的可能性更小。

intension.png

我們可以利用反三角函數(shù)求出角度來(lái)判斷下拉意圖。

JavaScript 中,反正切函數(shù)是 Math.atan(),需要注意的是,反正切函數(shù)算出的是弧度,我們還需要將它乘以 180 / π 才能獲取角度。

下面的代碼中,我們做了一個(gè)限制,只有角度小于 40 時(shí),我們才認(rèn)為用戶(hù)的真實(shí)意圖是想要下拉刷新。

const DEG_LIMIT = 40
function move(e{
  ...
  distanceY = endY - startY
  distanceX = endX - startX
  const deg = Math.atan(Math.abs(distanceX) / distanceY)
    * (180 / Math.PI)
  if (deg > DEG_LIMIT) {
    [startY, startX] = [endY, endX]
    return
  }
  ...
}

代碼示例

你可以在 codepen[4] 中查看效果,web 端需要按 F12 用手機(jī)瀏覽器打開(kāi)。

codepen.gif

總結(jié)

本文講解了下拉的原理、并根據(jù)原理寫(xiě)出初始代碼。在初始代碼的基礎(chǔ)上,我解決了 6 個(gè)缺陷、做了 2 個(gè)優(yōu)化,實(shí)現(xiàn)了一個(gè)完善的下拉刷新效果


該文章在 2024/3/30 0:12:59 編輯過(guò)
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專(zhuān)業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類(lèi)企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶(hù)的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
日韩一区二区在线|欧洲 | 最新国产在线观看精品 | 综合久久大伊人精品 | 亚洲精品伊人久久久大香 | 免费看国产大片AV | 中文字幕不卡高清视频在线 |