滑动结果的道理及实践一个滑动小插件

本文转载自blog

转载请申明出处

目次

  • 媒介

  • 基础道理

  • html组织

  • 实践

  • 小结

媒介

挪动端,滑动是很罕见的需求。许多同砚都用过swiper.js,本文从道理动身,实践出一个类swiper的滑动小插件ice-skating

小插件的例子:

在写代码的历程当中发生的一些思索:

  • 滑动的道理是什么

  • 怎样推断动画完成

  • 事宜绑定到哪一个元素,能否运用事宜托付优化

  • pc端和挪动端滑动有何差别

  • 正在举行的动画触摸时怎样获得当前款式

  • 怎样完成轮播

基础道理

滑动就是用transform: translate(x,y)或许transform: translate3d(x,y,z)去掌握元素的挪动,在放手的时刻剖断元素末了的位置,元素的款式运用transform: translate3d(endx , endy, 0)transition-duration: time来到达一个动画恢复的结果。规范浏览器供应transitionend事宜监听动画完毕,在完毕时将动画时候归零。

Note: 这里不议论非规范浏览器的完成,关于不支撑transformtransition的浏览器,能够运用position: absolute合营lefttop举行挪动,然后用基于时候的动画的算法来模仿动画结果。

html组织

举例一个基础的组织:

//example
<div class="ice-container">
    <div class="ice-wrapper" id="myIceId">
        <div class="ice-slide">Slide 1</div>
        <div class="ice-slide">Slide 2</div>
        <div class="ice-slide">Slide 3</div>
    </div>
</div>

transform: translate3d(x,y,z)就是运用在className为ice-slide的元素上。这里不展现css代码,能够在ice-skatingexample文件中里检察完全的css。css代码并非唯一的,简朴说只需完成下图的组织就能够。

《滑动结果的道理及实践一个滑动小插件》

从图中能够直观的看出,挪动的是绿色的元素。className为ice-slide的元素的宽乘于当前索引(offsetWidth * index),就是每次稳固时的偏移量。比方最最先transform: translate3d(offsetWidth * 0, 0, 0),切换到slide2后,transform: translate3d(offsetWidth * 1, 0, 0),大抵就是如许的历程。

实践

源码位于ice-skatingdist/iceSkating.js。我给插件起名叫ice-skating,愿望它像在冰面一样顺畅^_^

兼容各模块规范的容器

之前我们会将代码包裹在一个简朴的匿名函数里,如今须要加一些分外的代码来兼容种种模块规范。

(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';

})));

状况容器

用两个对象来存储信息

  • 一个页面能够实例化许多滑动对象,mainStore存储的是每一个对象的信息,比方宽高,设置参数之类的。

  • state存储的是触摸之类的暂时信息,每次触摸后都邑清空。

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

Object.create(null)建立的对象不会带有Object.prototype上的要领,由于我们不须要它们,比方toStringvalueOfhasOwnProperty之类的。

组织函数

function iceSkating(option){
    if (!(this instanceof iceSkating)) return new iceSkating(option);
}
iceSkating.prototype = { 
}

if (!(this instanceof iceSkating)) return new iceSkating(option);许多库和框架都有这句,简朴说就是不必new天生也能够天生实例。

触摸事宜

关于触摸事宜,在挪动端,我们会用touchEvent,在pc端,我们则用mouseEvent。所以我们须要检测支撑什么事宜。

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

支撑touch则以为是挪动端,否则为pc端

var events = ic.support.touch ? ['touchstart', 'touchmove', 'touchend']:['mousedown','mousemove','mouseup'];

声明事宜函数

pc端和挪动端这3个函数是通用的。

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);
    //默许阻挠容器元素的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函数每一个实例的容器都邑绑定,然则一切实例共用touchMovetouchEnd函数,它们只绑定在document,而且只会绑定一次。运用事宜托付有两个优点:

  1. 减少了元素绑定的事宜数,提高了机能。

  2. 假如将touchMovetouchEnd也绑定在容器元素上,当鼠标移出容器元素时,我们会“落空掌握”。在document上意味着能够“掌控全局”。

历程剖析

不会把封装的函数的代码都逐一列出来,但会申明它的作用。

触碰霎时

touchStart函数:

会在触碰的第一时候挪用,基础都在初始化state的信息

var touchStart = function(e){
    //mouse事宜会供应which值, e.which为3时示意按下鼠标右键,鼠标右键会触发mouseup,但右键不允许挪动滑块
     if (!ic.support.touch && 'which' in e && e.which === 3) return;
    //猎取肇端坐标。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;
    //时候戳
       state.startTime = e.timeStamp;
       //绑定事宜的元素
    state.currentTarget = e.currentTarget;
    state.id = e.currentTarget.id;
        //触发事宜的元素
    state.target = e.target;
    //猎取当前滑块的参数信息
        state.currStore = mainStore[e.currentTarget.id];
       //state的touchStart 、touchMove、touchEnd代表是不是进入该函数
    state.touchEnd = state.touchMove = false;
    state.touchStart = true;
       //示意滑块挪动的间隔
    state.diffX = state.diffY = 0;
       //动画运转时的坐标与动画运转前的坐标差值
    state.animatingX = state.animatingY = 0;
};

挪动

在挪动滑块时,能够滑块正在动画中,这是须要斟酌一种特殊情况。滑块的挪动应当根据如今的位置盘算。
怎样晓得动画运转中的信息呢,能够运用window.getComputedStyle(element, [pseudoElt]),它返回的款式是一个及时的 CSSStyleDeclaration 对象。用它取transform的值会返回一个 2D 变更矩阵,像如许matrix(1, 0, 0, 1, -414.001, 0),末了两位就是x,y值。

简朴封装一下,就能够获得当前动画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函数:

挪动时会延续挪用,假如只是点击操纵,不会触发touchMove。

var touchMove = function(e){
   // 1. 假如当前触发touchMove的元素和触发touchStart的元素不一致,不允许滑动。
   // 2. 实行touchMove时,需保证touchStart已实行,且touchEnd未实行。
  if(e.target !== state.target || state.touchEnd || !state.touchStart)    return;
  state.touchMove = true;
  //获得当前坐标
  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;
   //触摸时假如动画正在运转
  if(currStore.animating){
       // 获得当前元素translate的信息
        var animationTranslate = getTranslate(state.currentTarget);
        //盘算动画的偏移量,currStore.translateX和currStore.translateY示意的是滑块近来一次稳固时的translate值
        state.animatingX = animationTranslate.x - currStore.translateX;
        state.animatingY = animationTranslate.y - currStore.translateY;
        currStore.animating = false;
        //移除动画时候
        removeTransitionDuration(currStore.container);
    }
   //假如轮播举行中,将定时器消灭
   if(currStore.autoPlayID !== null){
        clearTimeout(currStore.autoPlayID);
        currStore.autoPlayID = null;
    }
   //推断挪动方向是程度照样垂直
   if(currStore.direction === 'x'){
        //currStore.touchRatio是挪动系数
        state.diffX = Math.round((currentX - state.startX) * currStore.touchRatio);
        //挪动元素
        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函数:

假如支撑translate3d,会优先运用它,translate3d会供应硬件加速。有兴致能够看看这篇blog两张图诠释CSS动画的机能

    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)');
        }
    };

触摸完毕

touchEnd函数:

在触摸完毕时挪用。

var touchEnd = function(e){
    state.touchEnd = true;
    if(!state.touchStart) return;
    var fastClick ;
    var currStore = state.currStore;
    //假如全部触摸历程时候小于fastClickTime,会以为此次操纵是点击。但默许是屏障了容器的click事宜的,所以供应一个clickCallback参数,会在点击操纵时挪用。
    if(fastClick = (e.timeStamp - state.startTime) < currStore.fastClickTime && !state.touchMove && typeof currStore.clickCallback === 'function'){
        currStore.clickCallback();
    }
    if(!state.touchMove) return;
    //假如挪动间隔没到达切换页的临界值,则让它恢复到近来的一次稳固状况
    if(fastClick || (Math.abs(state.diffX) < currStore.limitDisX && Math.abs(state.diffY) < currStore.limitDisY)){
    //在transitionend事宜绑定的函数中剖断是不是重启轮播,然则假如transform前后两次的值一样时,不会触发transitionend事宜,所以在这里剖断是不是重启轮播
    if(state.diffX === 0 && state.diffY === 0 && currStore.autoPlay) autoPlay(currStore);
       //恢复到近来的一次稳固状况
       recover(currStore, currStore.translateX, currStore.translateY, 0);
    }else{
        //位移满足切换
        if(state.diffX > 0 || state.diffY > 0) {
            //切换到上一个滑块
            moveTo(currStore, currStore.index - 1);
        }else{
            //切换到下一个滑块
            moveTo(currStore, currStore.index + 1);
        }    
    }
};

transitionDurationEndFn函数:

动画实行完成后挪用

var transitionDurationEndFn = function(){
    //将动画状况设置为false
    ic.store.animating = false;
    //实行自定义的iceEndCallBack函数
    if(typeof ic.store.iceEndCallBack === 'function')  ic.store.iceEndCallBack();
        //将动画时候归零
        transitionDuration(container, 0);
    //清空state
    if(ic.store.id === state.id) state = Object.create(null);
};

至此,一个完全的滑动历程完毕。

完成轮播

第一时候想到的是运用setInterval或许递归setTimeout完成轮播,但如许做并不文雅。

事宜轮回(EventLoop)中setTimeoutsetInterval会放入macrotask 行列中,内里的函数会放入microtask,当这个 macrotask 实行完毕后一切可用的 microtask 将会在同一个事宜轮回中实行。

我们极度的假定setInterval设定为200ms,动画时候设为1000ms。每隔200ms, macrotask 行列中就会插进去setInterval,但我们的动画此时没有完成,所以用setInterval或许递归setTimeout的轮播在这类情况下是有题目的。

最好思绪是在每次动画完毕后再将轮播开启。

//动画完毕实行的函数:
var transitionDurationEndFn = function(){
      ...
      //检测是不是开启轮播
      if(ic.store.autoPlay) autoPlay(ic.store);
};

轮播函数也相称简朴

var autoPlay = function(store){
       store.autoPlayID = setTimeout(function(){
        //当前滑块的索引
        var index = store.index;
        ++index;
        //到末了一个了,重置为0
        if(index === store.childLength){
           index = 0;
        }
        //挪动
        moveTo(store, index);
        },store.autoplayDelay);        
};

小结

本文记录了我思索的历程,代码应当另有许多处所值得完美。

    原文作者:aoyo
    原文地址: https://segmentfault.com/a/1190000008940043
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞