手把手教你用原生JavaScript造輪子(2)——輪播圖(更新:ES6版本)

csdoker發表於2019-02-16

通過上一篇文章的學習,我們基本掌握了一個輪子的封裝和開發流程。那麼這次將帶大家開發一個更有難度的專案——輪播圖,希望能進一步加深大家對於物件導向外掛開發的理解和認識。

So, Let's begin!

目前專案使用 ES5及UMD 規範封裝,所以在前端暫時只支援<script>標籤的引入方式,未來會逐步用 ES6 進行重構

演示地址:carousel carousel-mobile Github:csdwheels 如果覺得好用就點個Star吧~(〃'▽'〃)

carousel
carousel-mobile

Web輪播

思路分析

老規矩,在寫程式碼之前,我們需要對要開發的東西有個感性的認識,比如你可以先在腦中大致過一遍最終的專案效果是如何的,而在這裡你可以直接看上面的動態圖or專案頁面進行體驗。實際的開發階段之前,我們更要對外掛的邏輯思路有一個整體的分析,這樣在開發時才會更有效率,並且可以有效避免因為思路不清晰而導致的問題。

首先來看看Web輪播的效果及互動有哪些:

  1. 每隔一段時間自動輪播
  2. 左右箭頭可切換輪播
  3. 圓點可切換輪播
  4. 當滑鼠在輪播區域內時,輪播暫停;離開區域後,輪播重新開始
  5. 輪播切換時,會有一個勻速運動的動畫效果
  6. 當向右切換到最後一張時,會自動迴圈到第一張;向左切換到第一張時,迴圈到最後一張

如上幾點,可以說都是一個輪播圖必須實現的經典效果了。其他效果先忽略,第六點對於新手來說明顯是最有難度的,事實上這個效果有個常見的名字——無縫輪播。“無縫”也可以理解為無限迴圈,其實就是可以讓輪播朝著一個方向一直切換,並且自動在切換到頭尾圖片時迴圈。

比如現在有五張圖片,我們把它們編號為:

1 2 3 4 5

要實現上面的效果,你可能會想到在切換至頭尾時加個判斷,強制改變圖片位置,但是如果這麼做的話,當你最後一張圖切換回第一張圖時就會出現空白,因此還需要在頭尾分別新增一個尾部和頭部的元素作為位置改變時的過渡:

5 1 2 3 4 5 1

有了這兩張輔助圖,上面的效果就能順利實現了。到此,專案的基礎思路分析完畢,讓我們進入編碼階段吧!

基本架構

正式開始之前,還是需要先把專案的基本架構搭建起來:

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

    // ID-NAMES
    var ID = {
      CAROUSEL_WRAP: '#carouselWrap',
      CAROUSEL_DOTS: '#carouselDots',
      ARROW_LEFT: '#arrowLeft',
      ARROW_RIGHT: '#arrowRight'
    };

    var CLASS = {
      CAROUSEL_WRAP: 'carousel-wrap',
      CAROUSEL_IMG: 'carousel-image',
      CAROUSEL_DOTS_WRAP: 'carousel-buttons-wrap',
      CAROUSEL_DOTS: 'carousel-buttons',
      CAROUSEL_DOT: 'carousel-button',
      CAROUSEL_DOT_ON: 'carousel-button on',
      CAROUSEL_ARROW_LEFT: 'carousel-arrow arrow-left',
      CAROUSEL_ARROW_RIGHT: 'carousel-arrow arrow-right'
    };

    // Polyfills
    function addEvent(element, type, handler) {
      if (element.addEventListener) {
        element.addEventListener(type, handler, false);
      } else if (element.attachEvent) {
        element.attachEvent("on" + type, handler);
      } else {
        element["on" + type] = handler;
      }
    }

    // 合併物件
    function extend(o, n, override) {
      for (var p in n) {
        if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override))
          o[p] = n[p];
      }
    }

    // 輪播-建構函式
    var Carousel = function (selector, userOptions) {
      var _this = this;
      // 合併配置
      extend(this.carouselOptions, userOptions, true);
      // 獲取輪播元素
      _this.carousel = document.querySelector(selector);
      // 初始化輪播列表
      _this.carousel.appendChild(_this.getImgs());
      // 獲取輪播列表
      _this.carouselWrap = document.querySelector(ID.CAROUSEL_WRAP);
      // 每隔 50ms 檢測一次輪播是否載入完成
      var checkInterval = 50;
      var checkTimer = setInterval(function () {
        // 檢測輪播是否載入完成
        if (_this.isCarouselComplete()) {
          // 載入完成後清除定時器
          clearInterval(checkTimer);
          // 初始化輪播
          _this.initCarousel();
          // 初始化圓點
          _this.initDots();
          // 初識化箭頭
          _this.initArrows();
        }
      }, checkInterval);
    };
    // 輪播-原型物件
    Carousel.prototype = {
      carouselOptions: {
        // 是否顯示輪播箭頭
        showCarouselArrow: true,
        // 是否顯示輪播圓點
        showCarouselDot: true,
        // 輪播自動播放間隔
        carouselInterval: 3000,
        // 輪播動畫總時間
        carouselAnimateTime: 150,
        // 輪播動畫間隔
        carouselAnimateInterval: 10
      },
      isCarouselComplete: function () {
        // 檢測頁面圖片是否載入完成
        var completeCount = 0;
        for (var i = 0; i < this.carouselWrap.children.length; i++) {
          if (this.carouselWrap.children[i].complete) {
            completeCount++;
          }
        }
        return completeCount === this.carouselWrap.children.length ? true : false;
      }
    };
    return Carousel;
});
複製程式碼

addEvent()extend()函式上篇已經介紹過了,建構函式中各種配置項也都是專案中要用到的,不必多說。這裡的重點是checkTimer定時器,它的作用是每隔一定時間去檢查頁面上的圖片元素是否全部載入完畢,如果載入完畢再進行專案的初始化。 為什麼需要這麼做呢?因為我們的圖片元素是在JS中使用DOM動態載入的,所以可能會出現圖片還沒載入完成就執行了JS中的一些邏輯語句,導致不能通過DOM API正確獲取到圖片和對應元素屬性的現象。因此,在isCarouselComplete()函式中我們利用img元素的complete屬性,判斷當前頁面上的所有圖片是否載入完成,然後就能保證DOM屬性的正確獲取了。

初始化輪播

完成initCarousel()函式:

initCarousel: function(selector, userOptions) {
    // 獲取輪播數量
    this.carouselCount = this.carouselWrap.children.length;
    // 設定輪播
    this.setCarousel();
    // 初始化輪播序號
    this.carouselIndex = 1;
    // 初始化定時器
    this.carouselIntervalr = null;
    // 每次位移量 = 總偏移量 / 次數
    this.carouselAnimateSpeed = this.carouselWidth / (this.carouselOptions.carouselAnimateTime / this.carouselOptions.carouselAnimateInterval);
    // 判斷是否處於輪播動畫狀態
    this.isCarouselAnimate = false;
    // 判斷圓點是否點選
    this.isDotClick = false;
    // 繫結輪播圖事件
    this.bindCarousel();
    // 播放輪播
    this.playCarousel();
}
複製程式碼

通過this.carouselWidth / (this.carouselOptions.carouselAnimateTime / this.carouselOptions.carouselAnimateInterval)這個公式,可以計算出每次輪播動畫位移的偏移量,後面完成動畫函式時會用到。

setCarousel()裡進行輪播基本屬性的設定:

setCarousel: function () {
    // 複製首尾節點
    var first = this.carouselWrap.children[0].cloneNode(true);
    var last = this.carouselWrap.children[this.carouselCount - 1].cloneNode(true);
    // 新增過渡元素
    this.carouselWrap.insertBefore(last, this.carouselWrap.children[0]);
    this.carouselWrap.appendChild(first);
    // 設定輪播寬度
    this.setWidth(this.carousel, this.carouselOptions.carouselWidth);
    // 設定輪播高度
    this.setHeight(this.carousel, this.carouselOptions.carouselHeight);
    // 獲取輪播寬度
    this.carouselWidth = this.getWidth(this.carousel);
    // 設定初始位置
    this.setLeft(this.carouselWrap, -this.carouselWidth);
    // 設定輪播長度
    this.setWidth(this.carouselWrap, this.carouselWidth * this.carouselWrap.children.length);
}
複製程式碼

新增首尾的過渡元素、設定高度寬度等。

繫結輪播事件

然後是滑鼠移入移出事件的繫結:

playCarousel: function () {
    var _this = this;
    this.carouselIntervalr = window.setInterval(function() {
        _this.nextCarousel();
    }, this.carouselOptions.carouselInterval);
},
bindCarousel: function () {
    var _this = this;
    // 滑鼠移入移出事件
    addEvent(this.carousel, 'mouseenter', function(e) {
        clearInterval(_this.carouselIntervalr);
    });
    addEvent(this.carousel, 'mouseleave', function(e) {
        _this.playCarousel();
    });
}
複製程式碼

移入時停止輪播播放的定時器,移出後自動開始下一張的播放。

完成nextCarousel()prevCarousel()函式:

prevCarousel: function () {
    if (!this.isCarouselAnimate) {
        // 改變輪播序號
        this.carouselIndex--;
        if (this.carouselIndex < 1) {
            this.carouselIndex = this.carouselCount;
        }
        // 設定輪播位置
        this.moveCarousel(this.isFirstCarousel(), this.carouselWidth);
        if (this.carouselOptions.showCarouselDot) {
            // 顯示當前圓點
            this.setDot();
        }
    }
},
nextCarousel: function () {
    if (!this.isCarouselAnimate) {
        this.carouselIndex++;
        if (this.carouselIndex > this.carouselCount) {
            this.carouselIndex = 1;
        }
        this.moveCarousel(this.isLastCarousel(), -this.carouselWidth);
        if (this.carouselOptions.showCarouselDot) {
            // 顯示當前圓點
            this.setDot();
        }
    }
}
複製程式碼

功能是一樣的,改變輪播序號,然後進行輪播的移動。在moveCarousel()中完成對過渡元素的處理:

moveCarousel: function (status, carouselWidth) {
    var left = 0;
    if (status) {
        left = -this.carouselIndex * this.carouselWidth;
    } else {
        left = this.getLeft(this.carouselWrap) + carouselWidth;
    }
    this.setLeft(this.carouselWrap, left);
}
複製程式碼

輪播相關屬性和事件的設定就完成了。

繫結圓點事件

接下來是小圓點的事件繫結:

bindDots: function () {
    var _this = this;
    for (var i = 0, len = this.carouselDots.children.length; i < len; i++) {
        (function(i) {
            addEvent(_this.carouselDots.children[i], 'click', function (ev) {
                // 獲取點選的圓點序號
                _this.dotIndex = i + 1;
                if (!_this.isCarouselAnimate && _this.carouselIndex !== _this.dotIndex) {
                // 改變圓點點選狀態
                _this.isDotClick = true;
                // 改變圓點位置
                _this.moveDot();
                }
            });
        })(i);
    }
},
moveDot: function () {
    // 改變當前輪播序號
    this.carouselIndex = this.dotIndex;
    // 設定輪播位置
    this.setLeft(this.carouselWrap, -this.carouselIndex * this.carouselWidth);
    // 重設當前圓點樣式
    this.setDot();
},
setDot: function () {
    for (var i = 0, len = this.carouselDots.children.length; i < len; i++) {
        this.carouselDots.children[i].setAttribute('class', CLASS.CAROUSEL_DOT);
    }
    this.carouselDots.children[this.carouselIndex - 1].setAttribute('class', CLASS.CAROUSEL_DOT_ON);
}
複製程式碼

功能很簡單,點選圓點後,跳轉到對應序號的輪播圖,並重設小圓點樣式。

繫結箭頭事件

最後,還需要繫結箭頭事件:

bindArrows: function () {
    var _this = this;
    // 箭頭點選事件
    addEvent(this.arrowLeft, 'click', function(e) {
        _this.prevCarousel();
    });
    addEvent(this.arrowRight, 'click', function(e) {
        _this.nextCarousel();
    });
}
複製程式碼

這樣,一個沒有動畫的無縫輪播效果就完成了,見下圖:

carousel-test

實現動畫效果

上一節我們分析後的思路基本是實現了,但是輪播切換時的動畫效果又該怎麼實現呢?

既然要實現動畫,那我們先要找到產生動畫的源頭——即讓輪播發生切換的moveCarousel()函式。因此,我們需要先對它進行修改:

moveCarousel: function (target, speed) {
    var _this = this;
    _this.isCarouselAnimate = true;
    function animateCarousel () {
        if ((speed > 0 && _this.getLeft(_this.carouselWrap) < target) ||
            (speed < 0 && _this.getLeft(_this.carouselWrap) > target)) {
        _this.setLeft(_this.carouselWrap, _this.getLeft(_this.carouselWrap) + speed);
        timer = window.setTimeout(animateCarousel, _this.carouselOptions.carouselAnimateInterval);
        } else {
        window.clearTimeout(timer);
        // 重置輪播狀態
        _this.resetCarousel(target, speed);
        }
    }
    var timer = animateCarousel();
}
複製程式碼

改造之後的moveCarousel()函式接受兩個引數,target表示要移動到的輪播的位置,speed即為我們前面計算出的那個偏移量的值。然後通過animateCarousel()函式進行setTimeout()的遞迴呼叫,模擬出一種定時器的效果,當判斷未到達目標位置時繼續遞迴,如果到達後就重置輪播狀態:

// 不符合位移條件,把當前left值置為目標值
this.setLeft(this.carouselWrap, target);
//如當前在輔助圖上,就歸位到真的圖上
if (target > -this.carouselWidth ) {
    this.setLeft(this.carouselWrap, -this.carouselCount * this.carouselWidth);
}
if (target < (-this.carouselWidth * this.carouselCount)) {
    this.setLeft(this.carouselWrap, -this.carouselWidth);
}
複製程式碼

重置的過程和前面的實現是一樣的,不再贅述。完成新的moveCarousel()函式之後,還需要對prevCarousel()nextCarousel()進行改造:

prevCarousel: function () {
    if (!this.isCarouselAnimate) {
        // 改變輪播序號
        this.carouselIndex--;
        if (this.carouselIndex < 1) {
            this.carouselIndex = this.carouselCount;
        }
        // 設定輪播位置
        this.moveCarousel(this.getLeft(this.carouselWrap) + this.carouselWidth, this.carouselAnimateSpeed);
        if (this.carouselOptions.showCarouselDot) {
            // 顯示當前圓點
            this.setDot();
        }
    }
},
nextCarousel: function () {
    if (!this.isCarouselAnimate) {
        this.carouselIndex++;
        if (this.carouselIndex > this.carouselCount) {
            this.carouselIndex = 1;
        }
        this.moveCarousel(this.getLeft(this.carouselWrap) - this.carouselWidth,  -this.carouselAnimateSpeed);
        if (this.carouselOptions.showCarouselDot) {
            // 顯示當前圓點
            this.setDot();
        }
    }
},
複製程式碼

其實就替換了一下moveCarousel()呼叫的引數而已。完成這幾個函式的改造後,動畫效果初步實現了:

carousel-test1

優化動畫效果

在頁面上進行實際測試的過程中,我們可能偶爾會發現有卡頓的情況出現,這主要是因為用setTimeout()遞迴後模擬動畫的時候產生的(直接用setInterval()同樣會出現這種情況),所以我們需要用requestAnimationFrame這個HTML5的新API進行動畫效率的優化,再次改造moveCarousel()函式:

moveCarousel: function (target, speed) {
    var _this = this;
    _this.isCarouselAnimate = true;
    function animateCarousel () {
        if ((speed > 0 && _this.getLeft(_this.carouselWrap) < target) ||
            (speed < 0 && _this.getLeft(_this.carouselWrap) > target)) {
            _this.setLeft(_this.carouselWrap, _this.getLeft(_this.carouselWrap) + speed);
            timer = window.requestAnimationFrame(animateCarousel);
        } else {
            window.cancelAnimationFrame(timer);
            // 重置輪播狀態
            _this.resetCarousel(target, speed);
        }
    }
    var timer = window.requestAnimationFrame(animateCarousel);
}
複製程式碼

兩種方法的呼叫方式是類似的,但是在實際看起來,動畫卻流暢了不少,最重要的,它讓我們動畫的效率得到了很大提升。

到這裡,我們的開發就結束了嗎? 用上面的方式實現完動畫後,當你點選圓點時,輪播的切換是跳躍式的,並沒有達到我們開頭gif中那種完成後的效果。要讓任意圓點點選後的切換效果仍然像相鄰圖片一樣的切換,這裡還需要一種新的思路。

假如我們當前在第一張圖片,這時候的序號為1,而點選的圓點對應圖片序號為5的話,我們可以這麼處理:在序號1對應圖片節點的後面插入一個序號5對應的圖片節點,然後讓輪播切換到這張新增的圖片,切換完成後,立即改變圖片位置為真正的序號5圖片,最後刪除新增的節點,過程如下:

第一步:插入一個新節點 5 1 5 2 3 4 5 1

第二步:改變圖片位置,節點順序不變

第三步:刪除新節點,還原節點順序 5 1 2 3 4 5 1

用程式碼實現出來就是這樣的:

moveDot: function () {
    // 改變輪播DOM,增加過渡效果
    this.changeCarousel();
    // 改變當前輪播序號
    this.carouselIndex = this.dotIndex;
    // 重設當前圓點樣式
    this.setDot();
},
changeCarousel: function () {
    // 儲存當前節點位置
    this.currentNode = this.carouselWrap.children[this.carouselIndex];
    // 獲取目標節點位置
    var targetNode = this.carouselWrap.children[this.dotIndex];
    // 判斷點選圓點與當前的相對位置
    if (this.carouselIndex < this.dotIndex) {
        // 在當前元素右邊插入目標節點
        var nextNode = this.currentNode.nextElementSibling;
        this.carouselWrap.insertBefore(targetNode.cloneNode(true), nextNode);
        this.moveCarousel(this.getLeft(this.carouselWrap) - this.carouselWidth, -this.carouselAnimateSpeed);
    }
    if (this.carouselIndex > this.dotIndex) {
        // 在當前元素左邊插入目標節點
        this.carouselWrap.insertBefore(targetNode.cloneNode(true), this.currentNode);
        // 因為向左邊插入節點後,當前元素的位置被改變,導致畫面有抖動現象,這裡重置為新的位置
        this.setLeft(this.carouselWrap, -(this.carouselIndex + 1) * this.carouselWidth);
        this.moveCarousel(this.getLeft(this.carouselWrap) + this.carouselWidth, this.carouselAnimateSpeed);
    }
}
複製程式碼

需要注意的是,這裡要判斷點選的圓點序號與當前序號的關係,也就是在當前序號的左邊還是右邊,如果是左邊,還需要對位置進行重置。最後一步,完成新增節點的刪除函式resetMoveDot()

resetCarousel: function (target, speed) {
    // 判斷圓點是否點選
    if (this.isDotClick) {
        // 重置圓點點選後的狀態
        this.resetMoveDot(speed);
    } else {
        // 重置箭頭或者自動輪播後的狀態
        this.resetMoveCarousel(target);
    }
    this.isDotClick = false;
    this.isCarouselAnimate = false;
},
resetMoveDot: function (speed) {
    // 如果是圓點點選觸發動畫,需要刪除新增的過度節點並將輪播位置重置到實際位置
    this.setLeft(this.carouselWrap, -this.dotIndex * this.carouselWidth);
    // 判斷點選圓點和當前圓點的相對位置
    if (speed < 0) {
        this.carouselWrap.removeChild(this.currentNode.nextElementSibling);
    } else {
        this.carouselWrap.removeChild(this.currentNode.previousElementSibling);
    }
},
複製程式碼

檢視一下效果:

carousel-test2

大功告成!!

H5輪播

在Web版輪播的實現中,我們對位置的控制是直接使用元素絕對定位後的left值實現的,這種辦法雖然相容性好,但是效率相對是比較低的。在移動端版本的實現中,我們就可以不用考慮這種相容性的問題了,而可以儘量用更高效的方式實現動畫效果。

如果大家對CSS3有所瞭解,那想必一定知道transform這個屬性。從字面上來講,它就是變形,改變的意思,而它的值大致包括旋轉rotate扭曲skew縮放scale移動translate以及矩陣變形matrix等幾種型別。我們今天需要用到的就是translate,通過使用它以及transition等動畫屬性,可以更高效簡潔的實現移動端圖片輪播的移動。

由於基本思路與架構和Web版是差不多的,而H5版是基於Web版重寫的,所以這裡只說下需要改變的幾個地方。

替換Left的操作方法

既然是用新屬性來實現,那首先就要重寫setLeft()getLeft()方法,這裡我們直接替換為兩個新方法:

setLeft: function (elem, value) {
  elem.style.left = value + 'px';
},
getLeft: function (elem) {
  return parseInt(elem.style.left);
}

setTransform: function(elem ,value) {
  elem.style.transform =
    "translate3d(" + value + "px, 0px, 0px)";
  elem.style["-webkit-transform"] =
    "translate3d(" + value + "px, 0px, 0px)";
  elem.style["-ms-transform"] =
    "translate3d(" + value + "px, 0px, 0px)";
},
getTransform: function() {
  var x =
    this.carouselWrap.style.transform ||
    this.carouselWrap.style["-webkit-transform"] ||
    this.carouselWrap.style["-ms-transform"];
  x = x.substring(12);
  x = x.match(/(\S*)px/)[1];
  return Number(x);
}
複製程式碼

新版的方法功能與老版完全一直,只是實現所用到的方法不一樣了。接下來我們需要一個transition值的設定方法,通過這個動畫屬性,連requestAnimationFrame的相關操作也不需要了:

setTransition: function(elem, value) {
  elem.style.transition = value + 'ms';
}
複製程式碼

有了這三個方法,接下來就可以重寫moveCarousel()resetCarousel()resetMoveCarousel()方法了:

moveCarousel: function(target) {
  this.isCarouselAnimate = true;
  this.setTransition(this.carouselWrap, this.carouselOptions.carouselDuration);
  this.setTransform(this.carouselWrap, target);
  this.resetCarousel(target);
},
resetCarousel: function(target) {
  var _this = this;
  window.setTimeout(function() {
    // 重置箭頭或者自動輪播後的狀態
    _this.resetMoveCarousel(target);
    _this.isCarouselAnimate = false;
  }, _this.carouselOptions.carouselDuration);
},
resetMoveCarousel: function(target) {
  this.setTransition(this.carouselWrap, 0);
  // 不符合位移條件,把當前left值置為目標值
  this.setTransform(this.carouselWrap, target);
  //如當前在輔助圖上,就歸位到真的圖上
  if (target > -this.carouselWidth) {
    this.setTransform(this.carouselWrap, -this.carouselCount * this.carouselWidth);
  }
  if (target < -this.carouselWidth * this.carouselCount) {
    this.setTransform(this.carouselWrap, -this.carouselWidth);
  }
}
複製程式碼

之所以在每次setTransform()改變位置之前都要重新設定transition的值,是因為transition會使每次位置的改變都帶上動畫效果,而我們在程式碼中做的過渡操作又不希望使用者直接看到,因此,重設它的值後才能和以前的實現效果保持一致。

新增touch事件

在移動端上我們通常習慣用手指直接觸控螢幕來操作應用,所以Web端圓點和箭頭的互動方式這時候就顯得不那麼合適了,取而代之的,我們可以改寫成觸控的互動方式,也就是touch事件實現的效果:

bindCarousel: function() {
  var _this = this;
  // 滑鼠移入移出事件
  addEvent(this.carousel, "touchstart", function(e) {
    if (!_this.isCarouselAnimate) {
      clearInterval(_this.carouselIntervalr);
      _this.carouselTouch.startX = _this.getTransform();
      _this.carouselTouch.start = e.changedTouches[e.changedTouches.length - 1].clientX;
    }
  });
  addEvent(this.carousel, "touchmove", function(e) {
    if (!_this.isCarouselAnimate && _this.carouselTouch.start != -1) {
      clearInterval(_this.carouselIntervalr);
      _this.carouselTouch.move =
        e.changedTouches[e.changedTouches.length - 1].clientX - _this.carouselTouch.start;
      _this.setTransform(_this.carouselWrap, _this.carouselTouch.move + _this.carouselTouch.startX);
    }
  });
  addEvent(this.carousel, "touchend", function(e) {
    if (!_this.isCarouselAnimate && _this.carouselTouch.start != -1) {
      clearInterval(_this.carouselIntervalr);
      _this.setTransform(_this.carouselWrap, _this.carouselTouch.move + _this.carouselTouch.startX);
      var x = _this.getTransform();
      x +=
        _this.carouselTouch.move > 0
          ? _this.carouselWidth * _this.carouselTouch.offset
          : _this.carouselWidth * -_this.carouselTouch.offset;
      _this.carouselIndex = Math.round(x / _this.carouselWidth) * -1;
      _this.moveCarousel(
        _this.carouselIndex * -_this.carouselWidth
      );
      if (_this.carouselIndex > _this.carouselCount) {
        _this.carouselIndex = 1;
      }
      if (_this.carouselIndex < 1) {
        _this.carouselIndex = _this.carouselCount;
      }
      _this.playCarousel();
    }
  });
}
複製程式碼

簡單來說,我們把觸控事件分為三個過程——開始、移動、結束,然後在這三個過程中,就可以分別實現對應的邏輯與操作了:

  1. touchmove獲取觸控的起始點
  2. touchmove計算觸控後的偏移量
  3. 判斷偏移的方向,改變圖片位置

通過這套邏輯,我們模擬的移動裝置的觸控效果就能成功實現了:

carousel-test4

文章本身只是對專案整體思路和重點部分的講解,一些細節點也不可能面面俱到,還請大家對照原始碼自行理解學習~

最後我想說的是,類似輪播這樣的優秀外掛其實已經有很多了,但這並不妨礙我們寫一個自己的版本。因為只有自己寫一遍,並在腦中走一遍自己的思維過程,然後在學習一些優秀的原始碼及實現時才不至於懵圈。

到止為止,我們第二個輪子的開發也算順利完成了,所有原始碼已同步更新到github,如果大家發現有bug或其他問題,可以回覆在專案的issue中,我們們後會有期!(挖坑不填,逃。。

更新(2018-8-14)

已更新使用Webpack打包後的ES6版本,支援ES6模組化引入方式。

import { Carousel } from 'csdwheels'
import { CarouselMobile } from 'csdwheels'
複製程式碼

具體的使用方法請參考README

To be continued...

參考內容

相關文章