成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

滑動(dòng)效果的原理及實(shí)踐一個(gè)滑動(dòng)小插件

mcterry / 754人閱讀

摘要:支持則認(rèn)為是移動(dòng)端,否則為端聲明事件函數(shù)端和移動(dòng)端這個(gè)函數(shù)是通用的?;瑝K的移動(dòng)應(yīng)該依據(jù)現(xiàn)在的位置計(jì)算。

本文轉(zhuǎn)載自blog

轉(zhuǎn)載請(qǐng)注明出處

目錄

前言

基本原理

html結(jié)構(gòu)

實(shí)踐

小結(jié)

前言

移動(dòng)端,滑動(dòng)是很常見(jiàn)的需求。很多同學(xué)都用過(guò)swiper.js,本文從原理出發(fā),實(shí)踐出一個(gè)類swiper的滑動(dòng)小插件ice-skating。

小插件的例子:

移動(dòng)端

pc端

在寫代碼的過(guò)程中產(chǎn)生的一些思考:

滑動(dòng)的原理是什么

怎么判斷動(dòng)畫完成

事件綁定到哪個(gè)元素,可否使用事件委托優(yōu)化

pc端和移動(dòng)端滑動(dòng)有何不同

正在進(jìn)行的動(dòng)畫觸摸時(shí)怎么取得當(dāng)前樣式

如何實(shí)現(xiàn)輪播

基本原理

滑動(dòng)就是用transform: translate(x,y)或者transform: translate3d(x,y,z)去控制元素的移動(dòng),在松手的時(shí)候判定元素最后的位置,元素的樣式應(yīng)用transform: translate3d(endx , endy, 0)transition-duration: time來(lái)達(dá)到一個(gè)動(dòng)畫恢復(fù)的效果。標(biāo)準(zhǔn)瀏覽器提供transitionend事件監(jiān)聽(tīng)動(dòng)畫結(jié)束,在結(jié)束時(shí)將動(dòng)畫時(shí)間歸零。

Note: 這里不討論非標(biāo)準(zhǔn)瀏覽器的實(shí)現(xiàn),對(duì)于不支持transformtransition的瀏覽器,可以使用position: absolute配合lefttop進(jìn)行移動(dòng),然后用基于時(shí)間的動(dòng)畫的算法來(lái)模擬動(dòng)畫效果。

html結(jié)構(gòu)

舉例一個(gè)基本的結(jié)構(gòu):

//example
Slide 1
Slide 2
Slide 3

transform: translate3d(x,y,z)就是應(yīng)用在className為ice-slide的元素上。這里不展示css代碼,可以在ice-skating的example文件中里查看完整的css。css代碼并不是唯一的,簡(jiǎn)單說(shuō)只要實(shí)現(xiàn)下圖的結(jié)構(gòu)就可以。

從圖中可以直觀的看出,移動(dòng)的是綠色的元素。className為ice-slide的元素的寬乘于當(dāng)前索引(offsetWidth * index),就是每次穩(wěn)定時(shí)的偏移量。例如最開(kāi)始transform: translate3d(offsetWidth * 0, 0, 0),切換到slide2后,transform: translate3d(offsetWidth * 1, 0, 0),大致就是這樣的過(guò)程。

實(shí)踐

源碼位于ice-skating的dist/iceSkating.js。我給插件起名叫ice-skating,希望它像在冰面一樣順暢^_^

兼容各模塊標(biāo)準(zhǔn)的容器

以前我們會(huì)將代碼包裹在一個(gè)簡(jiǎn)單的匿名函數(shù)里,現(xiàn)在需要加一些額外的代碼來(lái)兼容各種模塊標(biāo)準(zhǔn)。

(function (global, factory) {
    typeof exports === "object" && typeof module !== "undefined" ? factory(exports) :
    typeof define === "function" && define.amd ? define(["exports"], factory) :
    (factory((global)));
}(this, (function (exports) { 
"use strict";

})));
狀態(tài)容器

用兩個(gè)對(duì)象來(lái)存儲(chǔ)信息

一個(gè)頁(yè)面可以實(shí)例化很多滑動(dòng)對(duì)象,mainStore存儲(chǔ)的是每個(gè)對(duì)象的信息,比如寬高,配置參數(shù)之類的。

state存儲(chǔ)的是觸摸之類的臨時(shí)信息,每次觸摸后都會(huì)清空。

var mainStore = Object.create(null);
var state = Object.create(null);

Object.create(null)創(chuàng)建的對(duì)象不會(huì)帶有Object.prototype上的方法,因?yàn)槲覀儾恍枰鼈儯?b>toString、valueOf、hasOwnProperty之類的。

構(gòu)造函數(shù)
function iceSkating(option){
    if (!(this instanceof iceSkating)) return new iceSkating(option);
}
iceSkating.prototype = { 
}

if (!(this instanceof iceSkating)) return new iceSkating(option);很多庫(kù)和框架都有這句,簡(jiǎn)單說(shuō)就是不用new生成也可以生成實(shí)例。

觸摸事件

對(duì)于觸摸事件,在移動(dòng)端,我們會(huì)用touchEvent,在pc端,我們則用mouseEvent。所以我們需要檢測(cè)支持什么事件。

iceSkating.prototype = {
    support: {
        touch: (function(){
            return !!(("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch);
        })()
    }

支持touch則認(rèn)為是移動(dòng)端,否則為pc端

var events = ic.support.touch ? ["touchstart", "touchmove", "touchend"]:["mousedown","mousemove","mouseup"];
聲明事件函數(shù)

pc端和移動(dòng)端這3個(gè)函數(shù)是通用的。

var touchStart = function(e){};
var touchMove = function(e){};
var touchEnd = function(e){};
初始化事件
var ic = this;
var initEvent = function(){
    var events = ic.support.touch ? ["touchstart", "touchmove", "touchend"]:  ["mousedown","mousemove","mouseup"];
    var transitionEndEvents = ["webkitTransitionEnd", "transitionend", "oTransitionEnd",   "MSTransitionEnd", "msTransitionEnd"];
    for (var i = 0; i < transitionEndEvents.length; i++) {
            ic.addEvent(container, transitionEndEvents[i], transitionDurationEndFn, false);
     } 
    ic.addEvent(container, events[0], touchStart, false);
    //默認(rèn)阻止容器元素的click事件
    if(ic.store.preventClicks) ic.addEvent(container, "click", ic.preventClicks, false);
    if(!isInit){
    ic.addEvent(document, events[1], touchMove, false);
    ic.addEvent(document, events[2], touchEnd, false);
    isInit = true;
    }
};

touchStarttransitionDurationEndFn函數(shù)每個(gè)實(shí)例的容器都會(huì)綁定,但是所有實(shí)例共用touchMovetouchEnd函數(shù),它們只綁定在document,并且只會(huì)綁定一次。使用事件委托有兩個(gè)好處:

減少了元素綁定的事件數(shù),提高了性能。

如果將touchMovetouchEnd也綁定在容器元素上,當(dāng)鼠標(biāo)移出容器元素時(shí),我們會(huì)“失去控制”。在document上意味著可以“掌控全局”。

過(guò)程分析

不會(huì)把封裝的函數(shù)的代碼都一一列出來(lái),但會(huì)說(shuō)明它的作用。

觸碰瞬間 touchStart函數(shù):

會(huì)在觸碰的第一時(shí)間調(diào)用,基本都在初始化state的信息

var touchStart = function(e){
    //mouse事件會(huì)提供which值, e.which為3時(shí)表示按下鼠標(biāo)右鍵,鼠標(biāo)右鍵會(huì)觸發(fā)mouseup,但右鍵不允許移動(dòng)滑塊
     if (!ic.support.touch && "which" in e && e.which === 3) return;
    //獲取起始坐標(biāo)。TouchEvent使用e.targetTouches[0].pageX,MouseEvent使用e.pageX。
    state.startX = e.type === "touchstart" ? e.targetTouches[0].pageX : e.pageX;
        state.startY = e.type === "touchstart" ? e.targetTouches[0].pageY : e.pageY;
    //時(shí)間戳
       state.startTime = e.timeStamp;
       //綁定事件的元素
    state.currentTarget = e.currentTarget;
    state.id = e.currentTarget.id;
        //觸發(fā)事件的元素
    state.target = e.target;
    //獲取當(dāng)前滑塊的參數(shù)信息
        state.currStore = mainStore[e.currentTarget.id];
       //state的touchStart 、touchMove、touchEnd代表是否進(jìn)入該函數(shù)
    state.touchEnd = state.touchMove = false;
    state.touchStart = true;
       //表示滑塊移動(dòng)的距離
    state.diffX = state.diffY = 0;
       //動(dòng)畫運(yùn)行時(shí)的坐標(biāo)與動(dòng)畫運(yùn)行前的坐標(biāo)差值
    state.animatingX = state.animatingY = 0;
};
移動(dòng)

在移動(dòng)滑塊時(shí),可能滑塊正在動(dòng)畫中,這是需要考慮一種特殊情況。滑塊的移動(dòng)應(yīng)該依據(jù)現(xiàn)在的位置計(jì)算。
如何知道動(dòng)畫運(yùn)行中的信息呢,可以使用window.getComputedStyle(element, [pseudoElt]),它返回的樣式是一個(gè)實(shí)時(shí)的 CSSStyleDeclaration 對(duì)象。用它取transform的值會(huì)返回一個(gè) 2D 變換矩陣,像這樣matrix(1, 0, 0, 1, -414.001, 0),最后兩位就是x,y值。

簡(jiǎn)單封裝一下,就可以取得當(dāng)前動(dòng)畫translate的x,y值了。

var getTranslate = function(el){
    var curStyle = window.getComputedStyle(el);
    var curTransform = curStyle.transform || curStyle.webkitTransform;
    var x,y; x = y = 0;
    curTransform = curTransform.split(", ");
    if (curTransform.length === 6) {
        x = parseInt(curTransform[4], 10);
        y = parseInt(curTransform[5], 10);
    }
       return {"x": x,"y": y};
};
touchMove函數(shù):

移動(dòng)時(shí)會(huì)持續(xù)調(diào)用,如果只是點(diǎn)擊操作,不會(huì)觸發(fā)touchMove。

var touchMove = function(e){
   // 1. 如果當(dāng)前觸發(fā)touchMove的元素和觸發(fā)touchStart的元素不一致,不允許滑動(dòng)。
   // 2. 執(zhí)行touchMove時(shí),需保證touchStart已執(zhí)行,且touchEnd未執(zhí)行。
  if(e.target !== state.target || state.touchEnd || !state.touchStart)    return;
  state.touchMove = true;
  //取得當(dāng)前坐標(biāo)
  var currentX = e.type === "touchmove" ? e.targetTouches[0].pageX : e.pageX;
  var currentY = e.type === "touchmove" ? e.targetTouches[0].pageY : e.pageY;
  var currStore = state.currStore;
   //觸摸時(shí)如果動(dòng)畫正在運(yùn)行
  if(currStore.animating){
       // 取得當(dāng)前元素translate的信息
        var animationTranslate = getTranslate(state.currentTarget);
        //計(jì)算動(dòng)畫的偏移量,currStore.translateX和currStore.translateY表示的是滑塊最近一次穩(wěn)定時(shí)的translate值
        state.animatingX = animationTranslate.x - currStore.translateX;
        state.animatingY = animationTranslate.y - currStore.translateY;
        currStore.animating = false;
        //移除動(dòng)畫時(shí)間
        removeTransitionDuration(currStore.container);
    }
   //如果輪播進(jìn)行中,將定時(shí)器清除
   if(currStore.autoPlayID !== null){
        clearTimeout(currStore.autoPlayID);
        currStore.autoPlayID = null;
    }
   //判斷移動(dòng)方向是水平還是垂直
   if(currStore.direction === "x"){
        //currStore.touchRatio是移動(dòng)系數(shù)
        state.diffX = Math.round((currentX - state.startX) * currStore.touchRatio);
        //移動(dòng)元素
        translate(currStore.container, state.animatingX + state.diffX +       state.currStore.translateX, 0, 0);
        }else{
            state.diffY = Math.round((currentY - state.startY) * state.currStore.touchRatio);
            translate(currStore.container, 0, state.animatingY + state.diffY + state.currStore.translateY, 0);
        }
    };
translate函數(shù):

如果支持translate3d,會(huì)優(yōu)先使用它,translate3d會(huì)提供硬件加速。有興趣可以看看這篇blog兩張圖解釋CSS動(dòng)畫的性能

    var translate = function(ele, x, y, z){
        if (ic.support.transforms3d){
            transform(ele, "translate3d(" + x + "px, " + y + "px, " + z + "px)");
        } else {
            transform(ele, "translate(" + x + "px, " + y + "px)");
        }
    };
觸摸結(jié)束 touchEnd函數(shù):

在觸摸結(jié)束時(shí)調(diào)用。

var touchEnd = function(e){
    state.touchEnd = true;
    if(!state.touchStart) return;
    var fastClick ;
    var currStore = state.currStore;
    //如果整個(gè)觸摸過(guò)程時(shí)間小于fastClickTime,會(huì)認(rèn)為此次操作是點(diǎn)擊。但默認(rèn)是屏蔽了容器的click事件的,所以提供一個(gè)clickCallback參數(shù),會(huì)在點(diǎn)擊操作時(shí)調(diào)用。
    if(fastClick = (e.timeStamp - state.startTime) < currStore.fastClickTime && !state.touchMove && typeof currStore.clickCallback === "function"){
        currStore.clickCallback();
    }
    if(!state.touchMove) return;
    //如果移動(dòng)距離沒(méi)達(dá)到切換頁(yè)的臨界值,則讓它恢復(fù)到最近的一次穩(wěn)定狀態(tài)
    if(fastClick || (Math.abs(state.diffX) < currStore.limitDisX && Math.abs(state.diffY) < currStore.limitDisY)){
    //在transitionend事件綁定的函數(shù)中判定是否重啟輪播,但是如果transform前后兩次的值一樣時(shí),不會(huì)觸發(fā)transitionend事件,所以在這里判定是否重啟輪播
    if(state.diffX === 0 && state.diffY === 0 && currStore.autoPlay) autoPlay(currStore);
       //恢復(fù)到最近的一次穩(wěn)定狀態(tài)
       recover(currStore, currStore.translateX, currStore.translateY, 0);
    }else{
        //位移滿足切換
        if(state.diffX > 0 || state.diffY > 0) {
            //切換到上一個(gè)滑塊
            moveTo(currStore, currStore.index - 1);
        }else{
            //切換到下一個(gè)滑塊
            moveTo(currStore, currStore.index + 1);
        }    
    }
};
transitionDurationEndFn函數(shù):

動(dòng)畫執(zhí)行完成后調(diào)用

var transitionDurationEndFn = function(){
    //將動(dòng)畫狀態(tài)設(shè)置為false
    ic.store.animating = false;
    //執(zhí)行自定義的iceEndCallBack函數(shù)
    if(typeof ic.store.iceEndCallBack === "function")  ic.store.iceEndCallBack();
        //將動(dòng)畫時(shí)間歸零
        transitionDuration(container, 0);
    //清空state
    if(ic.store.id === state.id) state = Object.create(null);
};

至此,一個(gè)完整的滑動(dòng)過(guò)程結(jié)束。

實(shí)現(xiàn)輪播

第一時(shí)間想到的是使用setInterval或者遞歸setTimeout實(shí)現(xiàn)輪播,但這樣做并不優(yōu)雅。

事件循環(huán)(EventLoop)中setTimeoutsetInterval會(huì)放入macrotask 隊(duì)列中,里面的函數(shù)會(huì)放入microtask,當(dāng)這個(gè) macrotask 執(zhí)行結(jié)束后所有可用的 microtask 將會(huì)在同一個(gè)事件循環(huán)中執(zhí)行。

我們極端的假設(shè)setInterval設(shè)定為200ms,動(dòng)畫時(shí)間設(shè)為1000ms。每隔200ms, macrotask 隊(duì)列中就會(huì)插入setInterval,但我們的動(dòng)畫此時(shí)沒(méi)有完成,所以用setInterval或者遞歸setTimeout的輪播在這種情況下是有問(wèn)題的。

最佳思路是在每次動(dòng)畫結(jié)束后再將輪播開(kāi)啟。

//動(dòng)畫結(jié)束執(zhí)行的函數(shù):
var transitionDurationEndFn = function(){
      ...
      //檢測(cè)是否開(kāi)啟輪播
      if(ic.store.autoPlay) autoPlay(ic.store);
};

輪播函數(shù)也相當(dāng)簡(jiǎn)單

var autoPlay = function(store){
       store.autoPlayID = setTimeout(function(){
        //當(dāng)前滑塊的索引
        var index = store.index;
        ++index;
        //到最后一個(gè)了,重置為0
        if(index === store.childLength){
           index = 0;
        }
        //移動(dòng)
        moveTo(store, index);
        },store.autoplayDelay);        
};
小結(jié)

本文記錄了我思考的過(guò)程,代碼應(yīng)該還有很多地方值得完善。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82350.html

相關(guān)文章

  • 滑動(dòng)效果原理實(shí)踐一個(gè)滑動(dòng)插件

    摘要:支持則認(rèn)為是移動(dòng)端,否則為端聲明事件函數(shù)端和移動(dòng)端這個(gè)函數(shù)是通用的?;瑝K的移動(dòng)應(yīng)該依據(jù)現(xiàn)在的位置計(jì)算。 本文轉(zhuǎn)載自blog 轉(zhuǎn)載請(qǐng)注明出處 目錄 前言 基本原理 html結(jié)構(gòu) 實(shí)踐 小結(jié) 前言 移動(dòng)端,滑動(dòng)是很常見(jiàn)的需求。很多同學(xué)都用過(guò)swiper.js,本文從原理出發(fā),實(shí)踐出一個(gè)類swiper的滑動(dòng)小插件ice-skating。 小插件的例子: 移動(dòng)端 pc端 在寫代碼的過(guò)程...

    Jrain 評(píng)論0 收藏0
  • 滑動(dòng)效果原理實(shí)踐一個(gè)滑動(dòng)插件

    摘要:支持則認(rèn)為是移動(dòng)端,否則為端聲明事件函數(shù)端和移動(dòng)端這個(gè)函數(shù)是通用的?;瑝K的移動(dòng)應(yīng)該依據(jù)現(xiàn)在的位置計(jì)算。 本文轉(zhuǎn)載自blog 轉(zhuǎn)載請(qǐng)注明出處 目錄 前言 基本原理 html結(jié)構(gòu) 實(shí)踐 小結(jié) 前言 移動(dòng)端,滑動(dòng)是很常見(jiàn)的需求。很多同學(xué)都用過(guò)swiper.js,本文從原理出發(fā),實(shí)踐出一個(gè)類swiper的滑動(dòng)小插件ice-skating。 小插件的例子: 移動(dòng)端 pc端 在寫代碼的過(guò)程...

    coordinate35 評(píng)論0 收藏0
  • 「碼個(gè)蛋」2017年200篇精選干貨集合

    摘要:讓你收獲滿滿碼個(gè)蛋從年月日推送第篇文章一年過(guò)去了已累積推文近篇文章,本文為年度精選,共計(jì)篇,按照類別整理便于讀者主題閱讀。本篇文章是今年的最后一篇技術(shù)文章,為了讓大家在家也能好好學(xué)習(xí),特此花了幾個(gè)小時(shí)整理了這些文章。 showImg(https://segmentfault.com/img/remote/1460000013241596); 讓你收獲滿滿! 碼個(gè)蛋從2017年02月20...

    wangtdgoodluck 評(píng)論0 收藏0
  • 一個(gè)JS效果竟然要研究一天,我是不是不適合做前端?

    摘要:緊接著就是導(dǎo)航欄的特效編寫,殊不知,就是這個(gè)效果,讓原本計(jì)劃上午完成的事情,愣是被我研究了大半天才解決。剛開(kāi)始我的布局是,導(dǎo)航欄是一個(gè),下面有八個(gè),分別是八個(gè)欄目。 showImg(https://segmentfault.com/img/bVYUar?w=720&h=537); 前言 今天這篇文章的標(biāo)題,顯然是要搞事情。一個(gè)JS交互效果,居然花費(fèi)了一天的寶貴時(shí)間才研究出來(lái),我是不是不...

    ztyzz 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<