9102,用原生js造一個輪播組件

原生js實現一個輪播組件

最近本身使用vue實現了carousel輪播組件,總體上的思路和以前的原生js有很大的區別,對應的動畫效果的實現是一個比較大的難點。這咱們用原生js來實現輪播組件,方便咱們對框架和原生實現方式有一個認知和對比,也正好複習一下原生和DOM相關的APIcss

在樣式方面,你們能夠發揮本身的想象力,也能夠參考社區其它的相似組件,這裏我實現了一個簡易樣式版本,供你們參考:html

vue版本的在這裏,而且支持移動端滑動切換:vue

在實現組件以前,咱們須要先了解一下有逢輪播和無縫輪播。css3

有縫輪播在輪播項正向移動到第一項或反向移動到最後一項時,會有一個回退效果。而無縫輪播會讓人感受不到這個切換效果。git

接下來咱們一步步經過原生js先實現一個有縫輪播,以後升級到無縫輪播,實現最終效果。github

有縫輪播

有縫輪播相對來講在實現思路上會簡單一些,主要是利用了css3提供的transitontranslate屬性來實現切換的動畫效果。web

首先搭建初始的頁面結構,效果大概是這樣的: 瀏覽器

slider-init

以後咱們會在這個基礎上讓1,2,3動起來

如今假設咱們是使用組件的用戶,咱們理想中的使用方式大概是這樣的:app

<div class="slider">
  <div class="slider-item">1</div>
  <div class="slider-item">2</div>
  <div class="slider-item">3</div>
</div>
複製代碼
const slider = new Slider('.wk-slider',options);
複製代碼

爲了頁面的美觀,咱們添加一些初始化樣式框架

/*初始化樣式*/
* {
  margin: 0;
  padding: 0;
}
*,
*::after,
*::before {
  box-sizing: inherit;
}
/* 通配符的css權重是最低的,html的標籤都會繼承box-sizing 而且對應元素進行盒模型更改的時候,對應的子元素也會更改 */
html {
  box-sizing: border-box;
}
複製代碼

用戶本身的樣式是這樣的:

/*用戶樣式*/
.demo-wrapper {
  margin: 40px;
}
.slider {
  margin: 0 auto;
  border: 4px solid black;
  overflow: hidden;
}
.slider-item {
  width: 400px;
  height: 200px;
  background-color: pink;
  font-size: 80px;
  text-align: center;
  line-height: 200px;
  color: #fff;
}
複製代碼

接下來咱們進行js邏輯的實現。

因爲組件在使用時第一個參數爲element,第二個參數爲options配置項,因此咱們的構造函數須要這樣寫:

class Slider {
  constructor (element, options) {
    this.slider = document.querySelector(element);
    this.options = options;
  }
}
複製代碼

以後咱們要獲取到輪播項的對應的寬度賦值到slider元素上,併爲對應的元素添加特定前綴的css類名,防止樣式衝突。並且因爲要讓整個子元素進行平移,還要將全部子元素放到一塊兒,而後再進行平移,並經過transition設置過渡動效。

咱們在構造函數的原型上添加對應的方法

initSliderStyle () {
  this.items = [...this.slider.children];
  // 這裏獲取寬度時要當心異步加載
  this.itemWidth = this.items[0].offsetWidth;
  this.slider.classList.add('wk-slider');
  this.slider.style.width = `${this.itemWidth}px`;
  this.createItemsWrapper();
}

createItemsWrapper () {
  this.itemsWrapper = document.createElement('div');
  this.itemsWrapper.classList.add('wk-slider-items-wrapper');
  this.slider.appendChild(this.itemsWrapper);
  this.items.map(item => {
    this.itemsWrapper.appendChild(item);
    item.classList.add('wk-slider-item');
  });
}
複製代碼

對應的組件css

/*組件樣式*/
.wk-slider {
  display: flex;
}
.wk-slider-item {
  flex-shrink: 0;
}
.wk-slider-items-wrapper {
  display: flex;
  flex-shrink: 0;
  transition: all 1s;
}
複製代碼

最後咱們爲子元素容器設置定時器,讓子元素動起來:

autoPlay () {
  if (!this.options.autoPlay) return;
  setInterval(() => {
    this.index++;
    this.go(this.index);
  }, 2000);
}

go (index) {
  const lastIndex = this.items.length - 1;
  if (index > lastIndex) {this.index = 0;}
  if (index < 0) {this.index = lastIndex;}
  this.itemsWrapper.style.transform = `translateX(${-this.itemWidth * this.index}px)`;
}
複製代碼

最終效果:

hasSpace

完整代碼以下:

class Slider {
  constructor (element, options) {
    this.slider = document.querySelector(element);
    this.options = options;
    this.index = 0;
    this.initSliderStyle();
    this.autoPlay();
  }

  initSliderStyle () {
    this.items = [...this.slider.children];
    // 這裏獲取寬度時要當心異步加載
    this.itemWidth = this.items[0].offsetWidth;
    this.slider.classList.add('wk-slider');
    this.slider.style.width = `${this.itemWidth}px`;
    this.createItemsWrapper();
  }

  createItemsWrapper () {
    this.itemsWrapper = document.createElement('div');
    this.itemsWrapper.classList.add('wk-slider-items-wrapper');
    this.slider.appendChild(this.itemsWrapper);
    this.items.map(item => {
      this.itemsWrapper.appendChild(item);
      item.classList.add('wk-slider-item');
    });
  }

  autoPlay () {
    if (!this.options.autoPlay) return;
    setInterval(() => {
      this.index++;
      this.go(this.index);
    }, 2000);
  }

  go (index) {
    const lastIndex = this.items.length - 1;
    const { itemsWrapper, itemWidth } = this;
    if (this.index > lastIndex) { this.lastToFirst(); }
    if (this.index < 0) { this.firstToLast(lastIndex); }
    itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
  }
}

const slider = new Slider('.slider', { autoPlay: true });
複製代碼
/*初始化樣式*/
* {
  margin: 0;
  padding: 0;
}
*,
*::after,
*::before {
  box-sizing: inherit;
}
/* 通配符的css權重是最低的,html的標籤都會繼承box-sizing 而且對應元素進行盒模型更改的時候,對應的子元素也會更改 */
html {
  box-sizing: border-box;
}

/*用戶樣式*/
.demo-wrapper {
  margin: 40px;
}
.slider {
  margin: 0 auto;
  border: 4px solid black;
  overflow: hidden;
}
.slider-item {
  width: 400px;
  height: 200px;
  background-color: pink;
  font-size: 80px;
  text-align: center;
  line-height: 200px;
  color: #fff;
}

/*組件樣式*/
.wk-slider {
  display: flex;
}
.wk-slider-item {
  flex-shrink: 0;
}
.wk-slider-items-wrapper {
  display: flex;
  flex-shrink: 0;
  transition: all 1s;
}
複製代碼

這樣一個簡單的有縫輪播組件就初步完成了。可是這裏咱們忽略了一個問題:當咱們的slide-item是圖片的時候,因爲圖片的異步加載,會致使獲取的寬度並不許確,我想到的解決方法是在onload事件以後再進行組件的使用:

window.onload = () => {
  const slider = new Slider('.slider', { autoPlay: true });
}
複製代碼

小夥伴也能夠本身想一些其它的解決方法,展示奇思妙想的時候到了。

無縫輪播

有縫輪播實際上是無縫輪播的一個升級版本,當圖片輪播到最後一項的時候,能夠繼續像以前同樣輪播到第一項,並不會有回退效果。

大概的一個思路以下:

  • 分別複製輪播的第一項和最後一項,而後再分別插入到最後一項和第一項
  • 若是是正向輪播:輪播到最後一項時,繼續輪播會進入複製的第一項,以後再閃動到真正的第一項,繼續輪播
  • 若是是逆向輪播:輪播到第一項時,會繼續輪播到複製的最後一項,以後閃動到真正的最後一項,繼續輪播

這裏咱們先複習幾個原生js語法:

  • Node.cloneNode(): 克隆一個節點,返回調用該方法的節點的一個副本。若是參數傳入true,會克隆當前節點的全部後代節點。若是爲false,則只克隆該節點自己。默認參數爲false
  • parentNode.insertBefore(newNode,referenceNode): 在參考節點以前插入一個擁有指定父節點的子節點。

接下來咱們一步一步來操做。

首先咱們分別複製第一項和最後一項插入到對應的位置:

appendCloneNode() {
  const first = this.items[0];
  const last = this.items[this.items.length - 1];
  const firstClone = first.cloneNode(true);
  const lastClone = last.cloneNode(true);
  this.slider.insertBefore(lastClone, first);
  this.slider.appendChild(firstClone);
  this.items = [...this.slider.children];
}
複製代碼

頁面佈局以下:

sliderNoSpace

以後,咱們要分別對最後一項到第一項以及第一項到最後一項進行處理。

// 最後一張到第一張
lastToFirst () {
  const { itemsWrapper, itemWidth } = this;
  // 將transition效果取消
  itemsWrapper.style.transition = 'none';
  // 將索引設置爲1
  this.index = 1;
  // 直接移動到第二張(沒有過渡效果)
  itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
  // 索引+1
  this.index++;
  itemsWrapper.offsetWidth;
  // 添加動畫效果,經過go方法移動到第三章
  itemsWrapper.style.transition = 'all 1s';
}
// 第一張到最後一張(代碼含義同上)
firstToLast (lastIndex) {
  const { itemsWrapper, itemWidth } = this;
  // 將transition效果取消
  itemsWrapper.style.transition = 'none';
  this.index = lastIndex - 1;
  itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
  this.index--;
  itemsWrapper.offsetWidth;
  itemsWrapper.style.transition = 'all 1s';
}
複製代碼

上邊的代碼是輪播的一個重要思路:將最後一張/第一張「閃動」到本身複製出來的哪一項,以後恢復過渡,繼續輪播

而這裏特別使人疑惑的一個地方:itemsWrapper.offsetWidth。總體上來看,這一行代碼和整個的邏輯並無任何關係,可是這裏涉及到有關瀏覽器重排、重繪的一個重要知識點:

DOM變更和樣式變更,都會觸發瀏覽器的從新渲染。可是,瀏覽器比較智能,會盡可能把全部的變更集中在一塊兒,排成一個隊列,而後一次性執行,儘可能避免屢次從新渲染。

接下來咱們回到上邊的代碼:瀏覽器智能的將itemsWrapper.style.transition="none"itemsWrapper.style.transition = "all 1s"進行合併後執行,而咱們要作的是強制瀏覽器進行從新渲染,讓這倆行代碼分別執行,而itemsWrapper.offsetWidth就能夠強制瀏覽器進行從新渲染。相似的屬性和方法還有不少,如下是我收集的一些文章,但願對你們的知識理解有幫助:

到這裏,主要的邏輯功能已經基本上完成了。接下來的工做相對來講就會很簡單了,我這裏再也不贅述,大概的工做是這樣的:

  • 添加左右箭頭
  • 添加下側點擊切換的控制條
  • 在鼠標移入輪播區域的時候中止輪播,鼠標離開輪播區域時繼續輪播

以後咱們能夠經過傳入的options來讓用戶本身控制是否支持自動輪播,是否須要左右箭頭和下側控制條,也可讓用戶自定義輪播的時間間隔。

相關文章
相關標籤/搜索