下拉刷新,上拉加載 的基礎款(基本實現)

前言

如今網上 下拉刷新,上拉加載 插件一搜一大堆,若是你想用在生產環境,那你能夠直接網上搜一個靠譜的,我所作的就是不依賴任何插件,一步一步把這個插件的過程寫一下,各位同窗能夠在此基礎上定製,沒有寫過插件的,能夠了解下插件怎麼寫的,整個過程定位入門級,看不懂?那麼百度插件直接用就行了,修煉一段時間在考慮怎麼寫插件吧。廢話很少說,上效果圖
圖片描述html

原理

下拉刷新的原理就是,添加一個 div,這個 div 裏面標識加載的過程,根據拉下的距離改變div的高度,從而顯示 下拉加載 的文字,鬆手後根據下拉的距離,判斷是否請求數據ios

構建過程

定義默認的一些配置項git

var defaults = {
    threshold: 100,   // 滑動觸發下拉刷新的距離
    stop: 40,         // 下拉刷新時停留的位置距離屏幕頂部的距離
    dis: 20           // 距離屏幕底端觸發 上拉加載 的距離
}

定義構造函數github

function JrRefresh (el, options) {
    this.options = Object.assign({}, defaults, options)  // 合併參數
    this.el = typeof el === 'string' ? document.querySelector(el) : el  // 定義要操做對象
    this.progress = null       // 下拉刷新顯示的 dom
    this.loadMore = null       // 上拉加載顯示的 dom
    this.progressHeight = 0    // 下拉刷新的 dom 的高度
    this.rotate = null         // 下拉刷新 轉圈 的角度
    this.touchstartY = 0       // 觸摸到屏幕的座標起始 Y 值
    this.currentY = 0          // 移動時實時記錄的座標 Y 值
    this.isAnimation = false   // 是否在自動回滾
    this.isRefresh = false     // 是否正在刷新數據
    this.isLoadMore = false    // 是否正在加載數據
    this.hasMore = true        // 是否有更多數據, 下拉加載會用
    this.rotateTimer = null    // 控制 下拉刷新 轉圈 的時間計時器
    this.event()
    this.init()
}

初始化,添加一些須要用到的 dom 元素瀏覽器

JrRefresh.prototype.init = function () {
    // 增長下拉刷新的顯示
    var refreshHtml = `<div class="jrscroll-downwarp jrscroll-downwarp-reset" style="height: 0px;"><div class="downwarp-content"><p class="downwarp-progress" style="transform: rotate(0deg);"></p ><p class="downwarp-tip">下拉刷新</p ></div></div>`
    var divm = document.createElement('div')
    divm.innerHTML = refreshHtml
    this.progress = divm.children[0]
    this.el.prepend(this.progress)
    // 增長上拉加載的顯示
    var loadMoreHtml = `<div class="jrscroll-upwarp" style="visibility: hidden;"><p class="upwarp-progress"></p><p class="upwarp-tip">加載中...</p></div>`
    var div = document.createElement('div')
    div.innerHTML = loadMoreHtml
    this.loadMore = div.children[0]
    this.el.appendChild(this.loadMore)
}

定義事件,解釋一下這裏用到的 bind 吧,這裏的 JrRefresh.prototype.pullDown、JrRefresh.prototype.touchend 等函數裏的 this 綁定到 JrRefresh 的構造函數上, 若是不用的話,pullDown 函數是由 this.el 調用的,this 會指向 this.el。(僞裝大家都懂了?)這是寫插件經常使用到一種綁定 this 的方法之一微信

JrRefresh.prototype.event = function () {
    this.el.addEventListener('touchstart', this.handleTouchStart.bind(this))
    this.el.addEventListener('touchmove', this.pullDown.bind(this))
    this.el.addEventListener('touchend', this.touchend.bind(this))
    window.addEventListener('scroll', this.handleScroll.bind(this))
}

手指觸摸時記錄Y值座標,用來判斷是向上滑仍是向下app

JrRefresh.prototype.handleTouchStart = function (e) {
    // 記錄手指觸摸屏幕的 Y 值座標
    this.touchstartY = e.changedTouches[0].clientY
}

手指開始滑動時,觸發 pullDown 事件,用 this.currentY 來記錄實時的 Y 值座標,當 this.currentY - this.touchstartY > 0 時,說明是手指向下滑動,這裏面 e.preventDefault() 是爲了防止微信還有 ios 的瀏覽器下拉時頂部出現出現默認的黑色空白,或者觸發 uc 等瀏覽器自帶的下拉刷新, 由 this.moveDown 專門來控制 下拉刷新顯示的dom(this.progress) 的高度dom

JrRefresh.prototype.pullDown = function (e) {
    var scrollTop = document.documentElement.scrollTop ||
                    window.pageYOffset ||
                    document.body.scrollTop
    this.currentY = e.targetTouches[0].clientY
    if (this.currentY - this.touchstartY >= 0 && scrollTop <= 0) {
        e.preventDefault()
        if (!this.isAnimation && !this.isRefresh) {
            this.moveDown(this.currentY - this.touchstartY)
        }
    }
}

moveDown 函數是用來專門控制 this.progress 的高度,rotateProgress 函數用來專門控制圈圈的旋轉角度 changeProgressState 函數用來專門控制this.progress 顯示內容函數

JrRefresh.prototype.moveDown = function (dis) {
    if (dis < this.options.threshold) {
        this.progress.style.height = this.progressHeight + dis + 'px'
        this.rotateProgress(dis*2)
        this.changeProgressState('下拉刷新')
    } else {
        // 當滑動距離超過 threshold 時,放慢下拉速度
        var aftDis = this.options.threshold + (dis - this.options.threshold) / 3
        var aftAngle = this.options.threshold * 2 + (dis - this.options.threshold) / 1.7
        this.progress.style.height = this.progressHeight + aftDis + 'px'
        this.rotateProgress(aftAngle)
        this.changeProgressState('釋放刷新')
    }
}

圈圈只有兩種狀況,一種隨手指移動,超過距離轉圈速度變慢,一種是本身不停轉,前一種狀況,手指下滑距離跟旋轉角度的比例並不必定按我這個寫法,主要有一點,就是轉動變慢時候,圈圈旋轉角度不要出現突變,這也是這個插件的難點之一,不懂的同窗多看多想哈this

JrRefresh.prototype.rotateProgress = function (rotate) {
    var rotateDom = this.progress.querySelector('.downwarp-progress')
    if (rotate != undefined) {
        rotateDom.style.transform = 'rotate(' + rotate + 'deg)'
        this.rotate = rotate
    } else {
        var t = 0;
        this.rotateTimer = setInterval(() => {
            t++
            var angle = (this.rotate + t*15) % 360
            rotateDom.style.transform = 'rotate(' + angle + 'deg)'
            rotateDom.style.WebkitTransform = 'rotate(' + angle + 'deg)'
        }, 16)
    }
}

changeProgressState 這個函數沒什麼好說的了

JrRefresh.prototype.changeProgressState = function (name) {
    this.progress.querySelector('.downwarp-tip').innerHTML = name
}

至此,下滑效果有點樣子了,下面是手指鬆開時候的邏輯

JrRefresh.prototype.touchend = function () {
    var scrollTop = document.documentElement.scrollTop ||
                    window.pageYOffset ||
                    document.body.scrollTop
    if (scrollTop > 0 || this.isRefresh|| this.isAnimation) return     //只有 1.在屏幕頂部 2.已完成請求數據 3.不在回滾 三條都知足才進行處理
    if ((this.currentY - this.touchstartY) > this.options.threshold) {
        this.options.downCallback()  // 觸發參數穿過來的請求數據
        this.isRefresh = true
        this.moveBack(this.options.stop)            // 下拉刷新時停留的位置距離屏幕頂部的距離

    } else  {
        this.moveBack()
    }

}

moveBack 函數專門把進度返回到對應位置

JrRefresh.prototype.moveBack = function (dis) {
    var dis = dis || 0;
    this.isAnimation = true   // 正在回退
    var currentHeight = this.progress.offsetHeight
    var t = 0,                        // 進行的步數
        b = 10,                       // 總步數
        c = (currentHeight - dis)/b   // 每一步的距離
    var timer = setInterval(() => {
        t++;
        this.progress.style.height = currentHeight - c * t + 'px'
        if (t == b) {
            if (dis === 0) {
                this.changeProgressState('下拉刷新')     
                this.progressHeight = 0
            } else {
                this.changeProgressState('正在刷新')
                this.progressHeight = this.options.stop
                this.rotateProgress()
            }
            this.touchstartY = ''
            this.isAnimation = false     // 回退完成
            clearInterval(timer) 
        }
    }, 16)
}

當請求數據完成,要回滾到原始位置,參數是 boolean 類型,代表有沒有更多數據

JrRefresh.prototype.endSuccess = function (bool) {
    if (this.isRefresh) {      //  若是是正在刷新數據
        this.changeProgressState('刷新成功')
        if (bool) {
            setTimeout(() => {     //延遲 500ms 回滾
                this.moveBack(0)
                this.isRefresh = false
                clearInterval(this.rotateTimer)
            },500)
        } else {
            this.toggleLoadingText(true)
        }
    }
    if (this.isLoadMore) {     //  若是是正在加載數據
        this.isLoadMore = false
        this.loadMore.style.visibility = 'hidden'
        this.toggleLoadingText(bool)
    }
}
JrRefresh.prototype.toggleLoadingText = function (hasMore) {
    if (hasMore) {
        this.loadMore.querySelector('.upwarp-tip').innerHTML = '加載中...'
        this.loadMore.querySelector('.upwarp-progress').style.display = 'inline-block'
    } else {
        this.loadMore.style.visibility = 'visible'
        this.loadMore.querySelector('.upwarp-tip').innerHTML = '沒有更多數據了'
        this.loadMore.querySelector('.upwarp-progress').style.display = 'none'
    }
}

至此,下拉刷新的邏輯終於完成了,下面開始上拉加載,比較麻煩的是獲取頁面高度,數據過來的同時請求頁面高度,頁面尚未渲染完成,頁面高度可能會不許,因此我處理方法是延遲100ms 獲取高度,

JrRefresh.prototype.handleScroll = function () {
    var top = this.loadMore.getBoundingClientRect().top;    // 獲取最底部標籤距離屏幕頂部的距離
    if (top + 10 < window.innerHeight && !this.isLoadMore && this.hasMore) {
        this.isLoadMore = true
        this.loadMore.style.visibility = 'visible'
        this.options.up.callback()
    }
}

用法

<div class="jrscroll"> 
//列表內容,如:<ul>列表數據</ul> ..
</div>
<script>
    var scroll = new JrRefresh(scrollWrap, {
        downCallback: pullDownRefresh,
        upCallback: pullUpLoadMore
    })
    function pullDownRefresh() {
        setTimeout(() => {
            console.log('刷新成功')
            // 處理數據
            scroll.endSuccess(true)
        }, 1000)
    }
    function pullUpLoadMore() {
        setTimeout(() => {
            console.log('請求成功')
            // 處理數據
            scroll.endSuccess(true)
        }, 2000)
    
    }
</script>

好了,大體就介紹這麼多,想看源碼的同窗,移步這裏,後續的完善和更新也會在 github 上,有興趣的同窗 star 或者 fork 哦

相關文章
相關標籤/搜索