成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

選擇器插件如何實(shí)現(xiàn)

這篇文章給大家分享的是有關(guān)選擇器插件如何實(shí)現(xiàn)的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。

創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),豐臺(tái)企業(yè)網(wǎng)站建設(shè),豐臺(tái)品牌網(wǎng)站建設(shè),網(wǎng)站定制,豐臺(tái)網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,豐臺(tái)網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

一個(gè)正常的選擇器插件是非常細(xì)致的,一步一步來(lái)描述就是。手指滑動(dòng)內(nèi)容跟隨手指滾動(dòng),當(dāng)內(nèi)容到底或觸頂?shù)臅r(shí)候就不能在滾動(dòng)并且內(nèi)容要一直保持在正確的位置上。

第一步分析插件結(jié)構(gòu)

首先要有一個(gè)插件容器,整個(gè)插件容器包含漸變背景,選中實(shí)線,內(nèi)容容器。效果類似于下面:

選擇器插件如何實(shí)現(xiàn)

所以對(duì)應(yīng)的代碼如下:

<div class="scroller-component" data-role="component">
    <div class="scroller-mask" data-role="mask"></div>
    <div class="scroller-indicator" data-role="indicator"></div>
    <div class="scroller-content" data-role="content">
        <div class="scroller-item" data-value='1'>1</div>
        <div class="scroller-item" data-value="2">2</div>
        <div class="scroller-item" data-value="3">3</div>
        <div class="scroller-item" data-value="4">4</div>
        <div class="scroller-item" data-value="5">5</div>
        <div class="scroller-item" data-value="6">6</div>
        <div class="scroller-item" data-value="7">7</div>
        <div class="scroller-item" data-value="8">8</div>
        <div class="scroller-item" data-value="9">9</div>
        <div class="scroller-item" data-value="10">10</div>
        <div class="scroller-item" data-value="11">11</div>
        <div class="scroller-item" data-value="12">12</div>
        <div class="scroller-item" data-value="13">13</div>
        <div class="scroller-item" data-value="14">14</div>
        <div class="scroller-item" data-value="15">15</div>
        <div class="scroller-item" data-value="16">16</div>
        <div class="scroller-item" data-value="17">17</div>
        <div class="scroller-item" data-value="18">18</div>
        <div class="scroller-item" data-value="19">19</div>
        <div class="scroller-item" data-value="20">20</div>
    </div>
</div>
* {
    margin: 0;
    padding: 0;
}
.scroller-component {
    display: block;
    position: relative;
    height: 238px;
    overflow: hidden;
    width: 100%;
}

.scroller-content {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    z-index: 1;
}

.scroller-mask {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    margin: 0 auto;
    width: 100%;
    z-index: 3;
    transform: translateZ(0px);
    background-image:
        linear-gradient(to bottom, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)),
        linear-gradient(to top, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6));
    background-position: top, bottom;
    background-size: 100% 102px;
    background-repeat: no-repeat;
}

.scroller-item {
    text-align: center;
    font-size: 16px;
    height: 34px;
    line-height: 34px;
    color: #000;
}

.scroller-indicator {
    width: 100%;
    height: 34px;
    position: absolute;
    left: 0;
    top: 102px;
    z-index: 3;
    background-image:
        linear-gradient(to bottom, #d0d0d0, #d0d0d0, transparent, transparent),
        linear-gradient(to top, #d0d0d0, #d0d0d0, transparent, transparent);
    background-position: top, bottom;
    background-size: 100% 1px;
    background-repeat: no-repeat;
}

.scroller-item {
    line-clamp: 1;
    -webkit-line-clamp: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

css 代碼主要作為樣式展示,通過(guò)外鏈的方式引入。這里就不做過(guò)多的解釋。

第二步實(shí)現(xiàn)手指滾動(dòng)容器

1.添加手指觸摸事件

let component = document.querySelector('[data-role=component]')

let touchStartHandler = (e) => { }
let touchMoveHandler = (e) => { }
let touchEndHandler = (e) => { }

component.addEventListener('touchstart', touchStartHandler)

component.addEventListener('touchmove', touchMoveHandler)

component.addEventListener('touchend', touchEndHandler)

這樣當(dāng)手指觸摸到 component 插件容器的時(shí)候就會(huì)觸發(fā)開(kāi)始,移動(dòng),結(jié)束事件。

2.分析手指滑動(dòng)容器移動(dòng)效果

手指上滑內(nèi)容上滑,手指下拉內(nèi)容下拉。只需要控制 content 的位置改動(dòng)的距離跟手指滑動(dòng)的距離保持一致即可。這里用到了 transform 樣式的 translate3d(x, y, z) 屬性。其中 x, z 保持不變,y的值就是手指移動(dòng)的值。

我們繼續(xù)做拆解,當(dāng)手指下拉時(shí) content 位置下移就會(huì)跟手勢(shì)保持一致。也就是 y 值變大(需要注意 y 軸正方向是往下的)。手指上拉正好與下拉上滑。當(dāng)再次下拉或上拉時(shí)內(nèi)容要在原來(lái)的基礎(chǔ)上保持不變。因此我們需要一個(gè)全局變量 __scrollTop 保存這個(gè)值。這個(gè)值等于用戶每次上拉下拉值的和,所以我們需要求出來(lái)用戶每次上拉下拉的值。

拆解用戶上拉的值,當(dāng)用戶手觸摸到屏幕的時(shí)候就會(huì)觸發(fā) touchstart 事件,移動(dòng)的時(shí)候會(huì)觸發(fā) touchmove 事件。離開(kāi)的時(shí)候會(huì)觸發(fā) touchend 事件。用戶上拉的初始值肯定是觸發(fā) touchstart 時(shí)手指的位置。結(jié)束值就是 touchend 時(shí)手指的位置。但是這樣就不能夠做到內(nèi)容跟隨手指實(shí)時(shí)運(yùn)動(dòng)了。所以需要拆解 touchmove 事件

touchmove 事件會(huì)在用戶手指運(yùn)動(dòng)的時(shí)候不停的觸發(fā),也就相當(dāng)于用戶多次極小的上下移動(dòng)。所以我們需要記錄下來(lái)用戶剛開(kāi)始時(shí)觸摸的位置。 __startTouchTop 。用手指當(dāng)前位置減去剛開(kāi)始觸發(fā)位置就是用戶移動(dòng)的距離 __scrollTop。具體代碼如下

let content = component.querySelector('[data-role=content]') // 內(nèi)容容器
let __startTouchTop = 0 // 記錄開(kāi)始滾動(dòng)的位置
let __scrollTop = 0 // 記錄最終滾動(dòng)的位置
// 這個(gè)方法下面馬上講解
let __callback = (top) => {
    const distance = top
    content.style.transform = 'translate3d(0, ' + distance + 'px, 0)'
}
// 這個(gè)方法下面馬上講解
let __publish = (top, animationDuration) => {
    __scrollTop = top
    __callback(top)
}
let touchStartHandler = (e) => {
    e.preventDefault()
    const target = e.touches ? e.touches[0] : e
    __startTouchTop = target.pageY
}
let touchMoveHandler = (e) => {
    const target = e.touches ? e.touches[0] : e
    let currentTouchTop = target.pageY
    let moveY = currentTouchTop - __startTouchTop
    let scrollTop = __scrollTop
    scrollTop = scrollTop + moveY
    __publish(scrollTop)
    __startTouchTop = currentTouchTop
}

注意1: touchstart 必須要記錄觸摸位置, touchend 可以不記錄。因?yàn)橛脩舻谝淮斡|摸的位置和下次觸摸的位置在同一個(gè)地方的可能性幾乎微乎其微,所以需要在 touchstart 里面重置觸摸位置。否則當(dāng)用戶重新觸摸的時(shí)候內(nèi)容會(huì)閃動(dòng)

**注意2:e.preventDefault() 方法是處理某些瀏覽器的兼容問(wèn)題并且能夠提高性能。像QQ瀏覽器用手指下拉的時(shí)候會(huì)出現(xiàn)瀏覽器描述導(dǎo)致方法失敗。 可以參考文檔 https://segmentfault.com/a/1190000014134234
https://www.cnblogs.com/ziyunfei/p/5545439.html**

上面的 touchMoveHandler 方法中出現(xiàn)了 __callback 的方法。這個(gè)方法是用來(lái)控制內(nèi)容容器的位置的, __publish 方法是對(duì)改變?nèi)萜魑恢玫囊粚臃庋b,可以實(shí)現(xiàn)跟用戶的手指動(dòng)作同步,也要實(shí)現(xiàn)用戶手指離開(kāi)之后位置不正確的判斷等。目前先實(shí)現(xiàn)跟隨用戶手指移動(dòng)

代碼到這里,你用瀏覽器調(diào)節(jié)成手機(jī)模式應(yīng)該已經(jīng)可以做到內(nèi)容跟隨鼠標(biāo)滾動(dòng)了,但是還存在很多問(wèn)題,下面會(huì)一一把這些問(wèn)題修復(fù)

第三步,限制手指滑動(dòng)最大值和最小值

目前用戶可以無(wú)限上拉下拉,很明顯是不對(duì)的。應(yīng)該當(dāng)?shù)谝粋€(gè)值稍微超越選中實(shí)線下方時(shí)就不能在下拉了,當(dāng)最后一個(gè)值稍微超越選中實(shí)線上方時(shí)就不能在上拉了。所以我們需要倆個(gè)值最小滾動(dòng)值: __minScrollTop 和最大滾動(dòng)值: __maxScrollTop

計(jì)算方式應(yīng)該是這個(gè)樣子:用戶下拉會(huì)產(chǎn)生一個(gè)最大值,而最大值應(yīng)該是第一個(gè)元素下拉到中間的位置。中間應(yīng)該就是元素容器中間的位置

let __maxScrollTop = component.clientHeight / 2 // 滾動(dòng)最大值

最小值應(yīng)該是用戶上拉時(shí)最后一個(gè)元素達(dá)到中間的位置,因此應(yīng)該是內(nèi)容容器-最大值。

let __minScrollTop =  - (content.offsetHeight - __maxScrollTop) // 滾動(dòng)最小值

最大值最小值有了,只需要在手指上拉下拉的過(guò)程中保證 __scrollTop 不大于或者不小于極值即可,因此在 touchMoveHandler 函數(shù)中補(bǔ)充如下代碼

if (scrollTop > __maxScrollTop || scrollTop < __minScrollTop) {
    if (scrollTop > __maxScrollTop) {
        scrollTop = __maxScrollTop
    } else {
        scrollTop = __minScrollTop
    }
}

第四步元素的位置準(zhǔn)確卡在選中實(shí)線中

目前手指抬起的時(shí)候元素停留的位置是存在問(wèn)題,這個(gè)也很容易理解。因?yàn)橐粋€(gè)元素是有高度的,當(dāng)你手指移動(dòng)的距離只要不是元素高度的整數(shù)倍他就會(huì)卡在選中實(shí)線上。因此我們只需要對(duì)移動(dòng)的距離除以元素的高度進(jìn)行四舍五入取整之后再乘以元素的高度就能夠保證元素位置是元素高得的倍數(shù)了

let indicator = component.querySelector('[data-role=indicator]')
let __itemHeight = parseFloat(window.getComputedStyle(indicator).height)

let touchEndHandler = () => { 
    let scrollTop = Math.round(__scrollTop / __itemHeight).toFixed(5) * __itemHeight
    __publish(scrollTop)
}

這樣子產(chǎn)生了倆個(gè)問(wèn)題,一是當(dāng)極值四舍五入之后超越了極值就會(huì)出錯(cuò),二是元素跳動(dòng)太大用戶體驗(yàn)不好。所以需要處理極值情況和添加動(dòng)畫(huà)滑動(dòng)效果

處理上面問(wèn)題中產(chǎn)生的極值問(wèn)題

我們新建一個(gè)函數(shù) __scrollTo 專門解決元素位置不對(duì)的問(wèn)題

// 滾動(dòng)到正確位置的方法
let __scrollTo = (top) => {
    top = Math.round((top / __itemHeight).toFixed(5)) * __itemHeight
    let newTop = Math.max(Math.min(__maxScrollTop, top), __minScrollTop)
    if (top !== newTop) {
        if (newTop >= __maxScrollTop) {
            top = newTop - __itemHeight / 2
        } else {
            top = newTop + __itemHeight / 2
        }
    }
    __publish(top, 250) // 這里傳入了第二個(gè)參數(shù)動(dòng)畫(huà)時(shí)長(zhǎng),先留一個(gè)伏筆。后面會(huì)講
}

簡(jiǎn)單分析一下,函數(shù)內(nèi)第一行跟之前的一樣。對(duì)位置進(jìn)行四舍五入變成元素高度的倍數(shù)。第二行判斷元素是否大于極值,如果大于最大值就取最大值,小于最小值就取最小值。當(dāng)滾動(dòng)值跟新的滾動(dòng)值不一樣的時(shí)候說(shuō)明用戶移動(dòng)超過(guò)了極值。然后進(jìn)行處理。大于等于最大值的時(shí)候元素的位置正好超出半個(gè)元素高度的,所以減掉高度的一半,小于最小值的時(shí)候恰好相反。添加一半

添加動(dòng)畫(huà)滑動(dòng)效果

這個(gè)比較麻煩,關(guān)于動(dòng)畫(huà)效果是可以單獨(dú)開(kāi)一章來(lái)說(shuō)的。這里我簡(jiǎn)單說(shuō)一下我這個(gè)動(dòng)畫(huà)的思路吧。盡量長(zhǎng)話短說(shuō)。

首先講解一下動(dòng)畫(huà)實(shí)現(xiàn)的原理,動(dòng)畫(huà)可以理解為多張連續(xù)的照片快速移動(dòng)超過(guò)眼睛可以捕獲的速度就會(huì)形成連貫的動(dòng)作。這就是我理解的動(dòng)畫(huà),像上面的 touchMoveHandler 方法其實(shí)是會(huì)被多次調(diào)用的,而且調(diào)用頻率非常的高,高到了幾毫秒調(diào)用一次,這個(gè)速度你肉眼肯定是分辨不出來(lái)的,而且每次移動(dòng)的距離賊短。所以你看起來(lái)就有了跟隨手指滾動(dòng)的效果

所以當(dāng)手指抬起的時(shí)候發(fā)現(xiàn)位置不正確這個(gè)時(shí)候應(yīng)該實(shí)現(xiàn)一個(gè)滾動(dòng)到正確位置的減速動(dòng)畫(huà)效果。這里我直接將 vux 里面的 animate.js 文件簡(jiǎn)化了一下直接拿過(guò)來(lái)用了

let running = {} // 運(yùn)行
let counter = 1 // 計(jì)時(shí)器
let desiredFrames = 60 // 每秒多少幀
let millisecondsPerSecond = 1000 // 每秒的毫秒數(shù)

const Animate = {
  // 停止動(dòng)畫(huà)
  stop (id) {
    var cleared = running[id] != null
    if (cleared) {
      running[id] = null
    }
    return cleared
  },

  // 判斷給定的動(dòng)畫(huà)是否還在運(yùn)行
  isRunning (id) {
    return running[id] != null
  },
  start (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
    let start = Date.now()
    let percent = 0 // 百分比
    let id = counter++
    let dropCounter = 0

    let step = function () {
      let now = Date.now()

      if (!running[id] || (verifyCallback && !verifyCallback(id))) {
        running[id] = null
        completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false)
        return
      }

      if (duration) {
        percent = (now - start) / duration
        if (percent > 1) {
          percent = 1
        }
      }
      let value = easingMethod ? easingMethod(percent) : percent
      if (percent !== 1 && ( !verifyCallback || verifyCallback(id))) {
        stepCallback(value)
        window.requestAnimationFrame(step)
      }
    }

    running[id] = true
    window.requestAnimationFrame(step)
    return id
  }
}

以上代碼作為一個(gè)js外鏈單獨(dú)引入,不知道取什么名就用 animate.js 好了。

簡(jiǎn)單講解一下,主要是弄了一個(gè)叫 Animate 的對(duì)象,里面包含三個(gè)屬性 stop, isRunning, start。 分別是停止動(dòng)畫(huà),動(dòng)畫(huà)是否在執(zhí)行,開(kāi)始一個(gè)動(dòng)畫(huà)。start 是關(guān)鍵,因?yàn)槠渌麄z個(gè)函數(shù)在這個(gè)項(xiàng)目中我都沒(méi)有用過(guò),哈哈。

start 函數(shù)包含很多個(gè)參數(shù),stepCallback:每次動(dòng)畫(huà)執(zhí)行的時(shí)候用戶處理的界面元素滾動(dòng)邏輯;verifyCallback:驗(yàn)證動(dòng)畫(huà)是否還需要進(jìn)行的函數(shù);completedCallback:動(dòng)畫(huà)完成時(shí)的回調(diào)函數(shù);duration:動(dòng)畫(huà)時(shí)長(zhǎng);easingMethod:規(guī)定動(dòng)畫(huà)的運(yùn)動(dòng)方式,像快進(jìn)慢出,快進(jìn)快出等等;root:不用管了,沒(méi)用到。

結(jié)束動(dòng)畫(huà)有倆種方式,第一種是傳入的動(dòng)畫(huà)時(shí)長(zhǎng)達(dá)成,另一種是驗(yàn)證動(dòng)畫(huà)是否還需要執(zhí)行的函數(shù)驗(yàn)證通過(guò)。否則動(dòng)畫(huà)會(huì)一直運(yùn)動(dòng)

有了動(dòng)畫(huà)函數(shù)了,接下來(lái)就是如何使用了。這里我們補(bǔ)充一下 __publish 函數(shù),并且添加一個(gè)是否開(kāi)啟動(dòng)畫(huà)的全局變量 __isAnimating 和 倆個(gè)曲線函數(shù) easeOutCubic, easeInOutCubic

let __isAnimating = false // 是否開(kāi)啟動(dòng)畫(huà)
// 開(kāi)始快后來(lái)慢的漸變曲線
let easeOutCubic = (pos) => {
    return (Math.pow((pos - 1), 3) + 1)
}
// 以滿足開(kāi)始和結(jié)束的動(dòng)畫(huà)
let easeInOutCubic = (pos) => {
    if ((pos /= 0.5) < 1) {
        return 0.5 * Math.pow(pos, 3)
    }
    return 0.5 * (Math.pow((pos - 2), 3) + 2)
}

let __publish = (top, animationDuration) => {
    if (animationDuration) {
        let oldTop = __scrollTop
        let diffTop = top - oldTop
        let wasAnimating = __isAnimating
        let step = function (percent) {
            __scrollTop = oldTop + (diffTop * percent)
            __callback(__scrollTop)
        }
        let verify = function (id) {
            return __isAnimating === id
        }
        let completed = function (renderedFramesPerSecond, animationId, wasFinished) {
            if (animationId === __isAnimating) {
                __isAnimating = false
            }
        }
        __isAnimating = Animate.start(step, verify, completed, animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic)
    } else {
        __scrollTop = top
        __callback(top)
    }
}

將上面的代碼補(bǔ)充完整你就會(huì)發(fā)現(xiàn)滾動(dòng)到正確位置的動(dòng)畫(huà)效果實(shí)現(xiàn)了,下面就講講實(shí)現(xiàn)的原理。

這里按照函數(shù)執(zhí)行的順序講解吧。 首先是定義的幾個(gè)變量, oldTop:用來(lái)保存元素的錯(cuò)誤位置; diffTop: 傳入的 top 是元素滾動(dòng)的正確位置; step, verify, completed 是 Animate 對(duì)象需要的三個(gè)回調(diào)函數(shù)。里面的參數(shù)先不用管后面會(huì)講,最下面給 __isAnimating 付了個(gè)值。 Animate.start 函數(shù)是有返回值的,返回值是當(dāng)前動(dòng)畫(huà)的ID

其中需要注意 wasAnimating ? easeOutCubic : easeInOutCubic這個(gè)。意思就是如果原來(lái)的動(dòng)畫(huà)存在就將 easeInOutCubic(倆頭慢中間快的參數(shù)傳入進(jìn)去)函數(shù)傳入進(jìn)去, 如果不存在就傳入進(jìn)去 easeOutCubic(開(kāi)始快后來(lái)慢)函數(shù)傳入進(jìn)去。符合的場(chǎng)景就是你手指快速滑動(dòng)抬起動(dòng)畫(huà)會(huì)執(zhí)行一段時(shí)間吧,這個(gè)過(guò)程動(dòng)畫(huà)就是從快到慢的過(guò)程,然后動(dòng)畫(huà)還沒(méi)結(jié)束你又接著快速滑動(dòng)是不是又從慢到快了。如果你不接著執(zhí)行是不是動(dòng)畫(huà)就由快到慢結(jié)束了。這里為啥傳入這倆個(gè)參數(shù)就不講解了,完全可以再開(kāi)一篇博客進(jìn)行講解比較麻煩。

step函數(shù),接受一個(gè) percent 翻譯過(guò)來(lái)是百分比的意思。 下面的第一行代碼

__scrollTop = oldTop + (diffTop * percent)

可以理解成, 老的位置 + 移動(dòng)的距離 * 百分比 就是新的位置。百分比一直增大當(dāng)百分比為百分之百的時(shí)候 __scrollTop === top。就實(shí)現(xiàn)了一個(gè)錯(cuò)誤位置到正確位置的過(guò)度。

百分比的計(jì)算方式是根據(jù)時(shí)間來(lái)計(jì)算的,然后被動(dòng)畫(huà)曲線進(jìn)行了加工

if (duration) {
    percent = (now - start) / duration
    if (percent > 1) {
      percent = 1
    }
}
let value = easingMethod ? easingMethod(percent) : percent

上面的是核心代碼。start 是調(diào)用Animate.start屬性的時(shí)候記錄的一個(gè)當(dāng)前時(shí)間,now是內(nèi)部函數(shù)執(zhí)行的時(shí)候記錄的一個(gè)當(dāng)前時(shí)間。 now - start 就是經(jīng)過(guò)了多長(zhǎng)時(shí)間,除以 duration動(dòng)畫(huà)時(shí)長(zhǎng)就可以得出動(dòng)畫(huà)時(shí)長(zhǎng)的百分比。下面判斷 easingMethod 是否傳入如果傳入了就對(duì)本來(lái)勻速增加的百分比進(jìn)行加工變成了動(dòng)畫(huà)曲線變化的百分比。

首先是 step 函數(shù),每次運(yùn)動(dòng)調(diào)用的函數(shù)。接受了一個(gè) percent ,翻譯過(guò)來(lái)是百分比意思。 在外面我定了一個(gè)幾個(gè)局部變量,分別是 oldTop: , , 正確位置減掉錯(cuò)誤位置也就是元素滾動(dòng)的距離。在 step 函數(shù)里賦予 __scrollTop 新值

step函數(shù)接受了一個(gè)叫百分比的參數(shù)。 用處就是當(dāng)元素不在正確位置的時(shí)候會(huì)產(chǎn)生一個(gè)值 __scrollTop, 而元素應(yīng)該的正確位置的值是 top,元素移動(dòng)的距離就是 diffTop = top - oldTop 如何一步一步的移動(dòng)到這個(gè)位置呢。就通過(guò)動(dòng)畫(huà)函數(shù)穿過(guò)來(lái)的這個(gè)百分比參數(shù)。這也是為啥在 __scrollTo 方法中調(diào)用 __publish 時(shí)加入第二個(gè)參數(shù)動(dòng)畫(huà)時(shí)長(zhǎng)的原因了,這樣就實(shí)現(xiàn)了一個(gè)自由滾動(dòng)的動(dòng)畫(huà)

verify函數(shù)接受一個(gè)當(dāng)前動(dòng)畫(huà)的id參數(shù),驗(yàn)證規(guī)則就是 __isAnimating === id 時(shí)說(shuō)明開(kāi)啟了下一個(gè)動(dòng)畫(huà) __isAnimating 就會(huì)改變。導(dǎo)致驗(yàn)證失敗,這個(gè)時(shí)候就會(huì)停止上一個(gè)動(dòng)畫(huà)

completed函數(shù)接受好幾個(gè)參數(shù),第一個(gè)參數(shù)是每秒多少幀,第二個(gè)參數(shù)是當(dāng)前動(dòng)畫(huà)id,第三個(gè)參數(shù)是完成狀態(tài)。這里主要用到了第二個(gè)參數(shù)當(dāng)前動(dòng)畫(huà)id。動(dòng)畫(huà)完成的時(shí)候應(yīng)該獎(jiǎng)動(dòng)畫(huà)id變?yōu)閒alse否則會(huì)一直走驗(yàn)證的邏輯。

第五步快速短暫觸摸,讓內(nèi)容自己快速動(dòng)起來(lái)

像目前內(nèi)容滑動(dòng)的距離基本是等于用戶手指觸摸的距離的,這樣就跟實(shí)際使用不符合,實(shí)際中手指使勁一滑內(nèi)容也會(huì)蹭蹭的滾動(dòng)。就目前這個(gè)樣子內(nèi)容一多也能累死用戶,所以需要添加用戶使勁滑動(dòng)內(nèi)容快速滾動(dòng)起來(lái)的邏輯

首先內(nèi)容自己快速動(dòng)起來(lái)很明顯是有個(gè)觸發(fā)條件的,這里的觸發(fā)條件是 touchEndHandler 函數(shù)執(zhí)行時(shí)的時(shí)間減去當(dāng)最后一次執(zhí)行 touchMoveHandler 函數(shù)的時(shí)間小于100毫秒。滿足這種狀態(tài)我們認(rèn)為用戶開(kāi)啟快速滾動(dòng)狀態(tài)。所以添加一個(gè)全局變量 __lastTouchMove 來(lái)記錄最后一次執(zhí)行 touchMoveHandler 函數(shù)的時(shí)間。

知道應(yīng)該快速滾動(dòng)了,如何判斷應(yīng)該滾動(dòng)多長(zhǎng)的距離呢?想一下當(dāng)前的條件,有一個(gè) __lastTouchMove 和執(zhí)行 touchEndHandler 函數(shù)的時(shí)間。這倆個(gè)是不是能夠的出來(lái)一個(gè)時(shí)間差。在想一下是不是有個(gè) __scrollTop 滾動(dòng)的位置,如果在獲取到上一個(gè)滾動(dòng)的位置是不是能夠得到一個(gè)位置差。那位置 / 時(shí)間是等于速度的。我們讓 __scrollTop + 速度 是不是可以得到新的位置。然后我們一直減小速度撿到最后等于 0 是不是就得到了滾動(dòng)的位置,并且能夠根據(jù)用戶的快速滑動(dòng)情況的出來(lái)應(yīng)該滾動(dòng)多長(zhǎng)的距離,用戶滑的越快速度越快距離越遠(yuǎn),相反的用戶滑動(dòng)的速度越慢距離越近

遺憾的是在 touchEndHandler 函數(shù)中拿不到目標(biāo)移動(dòng)的距離 pageY。所以我們需要在 touchMoveHandler 方法中做手腳,去記錄每次執(zhí)行這個(gè)方法時(shí)的時(shí)間和位置。所以我們?cè)偬砑右粋€(gè)全局變量 __positions 為數(shù)組類型。

// 上面提到的倆個(gè)全局變量的代碼
let __lastTouchMove = 0 // 最后滾動(dòng)時(shí)間記錄
let __positions = [] // 記錄位置和時(shí)間

然后我們將增加 __positions 的代碼添加到 touchMoveHandler 方法中

if (__positions.length > 40) {
    __positions.splice(0, 20)
}
__positions.push(scrollTop, e.timeStamp)

__publish(scrollTop)

__startTouchTop = currentTouchTop
__lastTouchMove = e.timeStamp

其中如果 __positions 的長(zhǎng)度超過(guò)40我們就取后20個(gè)。因?yàn)閿?shù)組太大占用內(nèi)存,而且循環(huán)遍歷的時(shí)候還非常浪費(fèi)時(shí)間。根據(jù)上面的邏輯我們手指快速移動(dòng)不會(huì)取時(shí)間過(guò)長(zhǎng)的數(shù)據(jù),所以20足夠了。當(dāng)有了寶貴的位置和時(shí)間數(shù)據(jù)我們就需要在 touchEndHandler 方法中分析出來(lái)移動(dòng)的速度了。這里我將完整的代碼先切出來(lái)。

let __deceleratingMove = 0 // 減速狀態(tài)每幀移動(dòng)的距離
let __isDecelerating = false // 是否開(kāi)啟減速狀態(tài)
let touchEndHandler = (e) => {
    if (e.timeStamp - __lastTouchMove < 100) { // 如果抬起時(shí)間和最后移動(dòng)時(shí)間小于 100 證明快速滾動(dòng)過(guò)
        let positions = __positions
        let endPos = positions.length - 1
        let startPos = endPos
        // 由于保存的時(shí)候位置跟時(shí)間都保存了, 所以 i -= 2
        // positions[i] > (self.__lastTouchMove - 100) 判斷是從什么時(shí)候開(kāi)始的快速滑動(dòng)
        for (let i = endPos; i > 0 && positions[i] > (__lastTouchMove - 100); i -= 2) {
            startPos = i
        }
        if (startPos !== endPos) {
            // 計(jì)算這兩點(diǎn)之間的相對(duì)運(yùn)動(dòng)
            let timeOffset = positions[endPos] - positions[startPos] // 快速開(kāi)始時(shí)間 - 結(jié)束滾動(dòng)時(shí)間
            let movedTop = __scrollTop - positions[startPos - 1] // 最終距離 - 快速開(kāi)始距離
            
            __deceleratingMove = movedTop / timeOffset * (1000 / 60) // 1000 / 60 代表 1秒60每幀 也就是 60fps。玩游戲的可能理解 60fps是啥意思
    
            let minVelocityToStartDeceleration = 4 // 開(kāi)始減速的最小速度 
            // 只有速度大于最小加速速度時(shí)才會(huì)出現(xiàn)下面的動(dòng)畫(huà)
            if (Math.abs(__deceleratingMove) > minVelocityToStartDeceleration) {
                __startDeceleration()
            }
        }
    }
    if (!__isDecelerating) {
        __scrollTo(__scrollTop)
    }
    
    __positions.length = 0
}

新添加了倆個(gè)全局變量運(yùn)動(dòng)速度和減速狀態(tài)記錄。當(dāng)減速狀態(tài)為true的時(shí)候肯定不能執(zhí)行 __scrollTo 方法的因?yàn)檫@倆個(gè)方法是沖突的。所以需要 __isDecelerating 記錄一下。里面新定義了一個(gè)函數(shù) __startDeceleration。 我們的減速方法也主要是在這個(gè)方法里面實(shí)現(xiàn)的。給你一下代碼

// 開(kāi)始減速動(dòng)畫(huà)
let __startDeceleration = () => {
    let step = () => {
        let scrollTop = __scrollTop + __deceleratingMove
        let scrollTopFixed = Math.max(Math.min(__maxScrollTop, scrollTop), __minScrollTop) // 不小于最小值,不大于最大值
        if (scrollTopFixed !== scrollTop) {
            scrollTop = scrollTopFixed
            __deceleratingMove = 0
        }
        if (Math.abs(__deceleratingMove) <= 1) {
            if (Math.abs(scrollTop % __itemHeight) < 1) {
                __deceleratingMove = 0
            }
        } else {
            __deceleratingMove *= 0.95
        }
        __publish(scrollTop)
    }
    let minVelocityToKeepDecelerating = 0.5
    let verify = () => {
        // 保持減速運(yùn)行需要多少速度
        let shouldContinue = Math.abs(__deceleratingMove) >= minVelocityToKeepDecelerating
        return shouldContinue
    }
    let completed = function (renderedFramesPerSecond, animationId, wasFinished) {
        __isDecelerating = false
        if (__scrollTop <= __minScrollTop || __scrollTop >= __maxScrollTop) {
            __scrollTo(__scrollTop)
            return
        }
    }
    __isDecelerating = Animate.start(step, verify, completed)
}

當(dāng)你把這些代碼都加進(jìn)去的時(shí)候,選擇器插件基本上就已經(jīng)完成了。下面講解一下這段讓你頭痛的代碼。

這里面用到了動(dòng)畫(huà),所以肯定包含三大回調(diào)函數(shù) step, verify, completed。然后一個(gè)一個(gè)講解一下

step函數(shù):這個(gè)函數(shù)是讓內(nèi)容一步一步運(yùn)動(dòng)的,這個(gè)函數(shù)基本上跟滾動(dòng)到正確位置的函數(shù)相似度很高。 新的位置是老位置 __scrollTop 加上每幀移動(dòng)的位置 __deceleratingMove。 然后讓每幀移動(dòng)的位置一直減少,但是需要注意 scrollTop 不能超出極值,所以做了最大值最小值判斷當(dāng)?shù)竭_(dá)極值的時(shí)候就將 __deceleratingMove 賦值為0 。

if (Math.abs(__deceleratingMove) <= 1) {
    if (Math.abs(scrollTop % __itemHeight) < 1) {
        __deceleratingMove = 0
    }
}

這段代碼,你可能佷懵。他的作用是當(dāng)滾動(dòng)的位置沒(méi)有到達(dá)極值的時(shí)候如何讓他卡在正確位置上。 Math.abs(__deceleratingMove) 這是每幀移動(dòng)的距離的絕對(duì)值。當(dāng)他小于1的時(shí)候說(shuō)明移動(dòng)的距離已經(jīng)非常小了,用戶基本上都察覺(jué)不到移動(dòng)了。然后再用新位置對(duì)元素高度取余,如果余數(shù)為0表示正好卡在正確位置上,但是即使稍微比 0 大那么一丟丟也看不出來(lái),而且基本不會(huì)那么巧取到 0,所以當(dāng)余數(shù)滿足小于 1 的時(shí)候講每幀移動(dòng)的距離賦值為0.

verify函數(shù):定義了一個(gè)最小每幀移動(dòng)距離的局部變量 minVelocityToKeepDecelerating, 當(dāng) __deceleratingMove 值小于他的時(shí)候說(shuō)明用戶基本上不會(huì)發(fā)現(xiàn)內(nèi)容還在移動(dòng)可以停下來(lái)了。

completed函數(shù):既然是完成函數(shù)就一定要將 __isDecelerating 參數(shù)變?yōu)閒alse,否則下次進(jìn)行的不是快速移動(dòng)內(nèi)容就沒(méi)法跑到正確位置上了。這里多加了一步是否是極值的判斷,如果是極值就執(zhí)行 __scrollTo 函數(shù)到正確位置上。

感謝各位的閱讀!關(guān)于選擇器插件如何實(shí)現(xiàn)就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

網(wǎng)頁(yè)題目:選擇器插件如何實(shí)現(xiàn)
URL標(biāo)題:http://jinyejixie.com/article30/pgidso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、品牌網(wǎng)站制作、品牌網(wǎng)站設(shè)計(jì)、網(wǎng)站設(shè)計(jì)、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名
新田县| 鄂尔多斯市| 如皋市| 衡水市| 都昌县| 汉川市| 自治县| 尚义县| 融水| 荃湾区| 布尔津县| 桓仁| 融水| 肥城市| 金坛市| 扎赉特旗| 南投县| 隆回县| 满洲里市| 昭觉县| 辽阳市| 天峨县| 台北县| 鄂托克前旗| 边坝县| 嫩江县| 金乡县| 长宁县| 酒泉市| 黑龙江省| 甘肃省| 彰化县| 农安县| 曲沃县| 诸暨市| 镶黄旗| 通道| 荥经县| 罗田县| 鸡西市| 金塔县|