ES6! 如何製做一個高效輪播圖?

Make a lún'bō'tú works

輪播圖千種萬種,怎樣才能作出符合要求的輪播圖?原理上天入地,如何優化才能達到極限絲滑?本文做者將解答這一切,經過現場製做一個輪播圖,帶你詳細瞭解、理解,製做 All kinds of 高性能輪播圖 !javascript

仿自 Google Playcss

不過,在事實上,輪播圖的點擊率一般都很低,不多能引發用戶的注意,而卻每每佔用了頁面某個極重要的位置。你的網站真的須要一個輪播圖嗎?輕輕問本身三聲,谷歌一下對輪播圖效果的相關調查和建議,再決定是否要着手製做你的輪播圖。html

2017.8.20 更新——————————
1. 代碼簡潔化 & 語言精簡
2. 刪去不被推薦的有限部分
3. API 重寫java

! ES6 API 重寫
ES6 啊,,牛逼啊!我TM要火啊!!
然而並無。瀏覽器

開始

1. 結構函數

div.father包裹圖片。div.viewport爲視口部分。性能

<div class="viewport" id="example">
  <div class="father">
    <div>A</div><!-- 1 -->
    <div>B</div>
    <div>C</div><!-- 3 -->
    <div>D</div>
    <div>E</div><!-- 5 -->
  </div>
  <div class="mother">左</div>
  <div class="mother">右</div>
</div>
.viewport {
  width: 900px;
  height: 300px;
  overflow: hidden;
  position: relative;
}
.father {
  height: inherit;
  width: 3000%; /* 子元素 float 沒法撐開 */
  transform: translate3d(0, 0, 0);
  transition: transform 0.3s ease-in-out;
}
.father > div {
  width: 550px;
  height: inherit;
  float: left;
}
.mother {
  width: 30px;
  height: inherit;
  line-height: 300px;
  text-align: center;
  cursor: pointer;
  user-select:none;
  background: rgba(0,0,0,0.15);
  position: absolute;top: 0;
} .mother.left { left: 0 } .mother.right { right: 0 }

transform: translate3d()使用 GPU 加速。測試

2. 代碼實現優化

class Lunbo {
  constructor(element) {
    this.viewport = element;
    this.father = element.children[0];
    this.photos = this.father.children;
    // 自設的圖片寬, 包括 margin
    this.photoWidth = this.photos[0].offsetWidth + parseInt(getComputedStyle(this.photos[0]).marginLeft) + parseInt(getComputedStyle(this.photos[0]).marginRight);

    // 註冊移動事件
    element.children[1].addEventListener('click', this.left.bind(this));
    element.children[2].addEventListener('click', this.right.bind(this));
  }

  load() {

  }

  left() {
    this.load(this.showingId - 1);
  }

  right() {
    this.load(this.showingId + 1);
  }
}
  1. 頁面加載時:選取一張做爲焦點
    切換時fatherGo(to)負責跳轉到指定的焦點圖;
  2. 高效 & 無限輪播

(此處如下全部代碼僅顯示添加 / 修改部分)
思路也是難點。一題,這樣解決:動畫

class Lunbo {
  constructor(element) {
    // (可視寬 -焦點圖片寬) / 2,焦點圖到視口左或右的距離
    this.partnerWidth = (this.viewport.clientWidth - this.photoWidth) / 2;
  }

  // 計算移動距離
  countX(id) {
    return -id * this.photoWidth + this.partnerWidth;
  }

  // 切換 / 載入 / 移動圖片。無參數則除法求整,僅用來切換到一個瞎選的初始焦點
  load(newId = parseInt(this.photos.length / 2) - 1) {
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;
    this.showingId = newId;
  }
}
// 切換至初始焦點
const Example = new Lunbo(document.getElementById("example"));
Example.load();

countX(id) 解釋:

若將 Id = 2 對應圖片(第 3 張)做焦點,向左挪過去兩張(此時該圖靠最左),後加回partnerWidth

二題:

<div class="father" id="father">
  <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>

  <div>A</div>
  <div>B</div>
  <div>C</div>
  <div>D</div>
  <div>E</div>

  <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>
</div>

三倍於展現圖,JS 動態生成亦可。稱之三個塊。

.moving { transition: none }

接近塊間距時關閉動畫移至另外一塊相應位置。

class Lunbo {
  constructor(element) {
    // 表示接近邊緣的圖片 Id。接近左邊緣的即第2 張圖,右邊緣的則爲倒數第二張
    this.closeLeftId = 1;
    this.closeRightId = this.photos.length - 2;

    this.photosQuantity = this.photos.length / 3;

    // 當運動到上面兩個 Id 時默默移動到的對應 Id
    // 接近左邊時跳轉到右邊塊的第二張
    // 接近右邊則跳轉到左邊塊的倒數第二張
    this.backLeftId = this.photosQuantity - 2;
    this.backRightId = this.photosQuantity * 2 + 1;
  }

  load(newId = parseInt(this.photos.length / 2) - 1) {
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;

    if (newId === this.closeLeftId){
      newId = this.backRightId;
    } else if (newId === this.closeRightId){
      newId = this.backLeftId;
    } else {
      this.showingId = newId;
      return;
    }
    this.father.addEventListener('transitionend', this.backMove.bind(this, newId), {once: true});
  }

  backMove(newId) {
    this.father.classList.add("moving");
    this.father.clientWidth();
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;
    this.father.clientWidth();
    this.father.classList.remove("moving");
    this.showingId = newId;
  }
}

4. 整理代碼

<!DOCTYPE html><html><head>
<title>17.8.20</title>
<style>
html,body { height: 100% }
.viewport {
  width: 900px;height: 300px;
  overflow: hidden;
  position: relative;
}
.father {
  height: inherit;
  width: 3000%;
  transform: translate3d(0, 0, 0);
  transition: transform 0.3s ease-in-out;
} .father.moving { transition: none }
.father > div {
  width: 550px;height: inherit;background: #aaa;
  float: left;
}
.mother {
  width: 30px;
  height: inherit;
  line-height: 300px;
  text-align: center;
  cursor: pointer;
  user-select:none;
  background: rgba(0,0,0,0.15);
  position: absolute;top: 0;
} .mother.left { left: 0 } .mother.right { right: 0 }
</style></head><body>
<div class="viewport" id="example">
  <div class="father">
    <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>

    <div>A</div>
    <div>B</div>
    <div>C</div>
    <div>D</div>
    <div>E</div>

    <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>
  </div>
  <div class="mother left">左</div>
  <div class="mother right">右</div>
</div>
<script>
class Lunbo {
  constructor(element) {
    this.viewport = element;
    this.father = element.children[0];
    this.photos = this.father.children;
    // 自設的圖片寬, 包括 margin
    this.photoWidth = this.photos[0].offsetWidth + parseInt(getComputedStyle(this.photos[0]).marginLeft) + parseInt(getComputedStyle(this.photos[0]).marginRight);

    // (可視寬 -焦點圖片寬) / 2,焦點圖到視口左或右的距離
    this.partnerWidth = (this.viewport.clientWidth - this.photoWidth) / 2;

    // 表示接近邊緣的圖片 Id。接近左邊緣的即第2 張圖,右邊緣的則爲倒數第二張
    this.closeLeftId = 1;
    this.closeRightId = this.photos.length - 2;

    this.photosQuantity = this.photos.length / 3;

    // 當運動到上面兩個 Id 時默默移動到的對應 Id
    // 接近左邊時跳轉到右邊塊的第二張
    // 接近右邊則跳轉到左邊塊的倒數第二張
    this.backLeftId = this.photosQuantity - 2;
    this.backRightId = this.photosQuantity * 2 + 1;

    // 註冊移動事件
    element.children[1].addEventListener('click', this.left.bind(this));
    element.children[2].addEventListener('click', this.right.bind(this));
  }

  // 計算移動距離
  countX(id) {
    return -id * this.photoWidth + this.partnerWidth;
  }

  // 切換 / 載入 / 移動圖片。無參數則除法求整,僅用來切換到一個瞎選的初始焦點
  load(newId = parseInt(this.photos.length / 2) - 1) {
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;

    if (newId === this.closeLeftId){
      newId = this.backRightId;
    } else if (newId === this.closeRightId){
      newId = this.backLeftId;
    } else {
      this.showingId = newId;
      return;
    }
    this.father.addEventListener('transitionend', this.backMove.bind(this, newId), {once: true});
  }

  backMove(newId) {
    this.father.classList.add("moving");
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;

    this.father.clientWidth;

    this.father.classList.remove("moving");
    this.showingId = newId;
  }

  left() {
    this.load(this.showingId - 1);
  }

  right() {
    this.load(this.showingId + 1);
  }
}

// 切換至初始焦點
const Example = new Lunbo(document.getElementById("example"));
Example.load();
</script></body></html>

代碼已經過測試。你須要碼更多的代碼,兼容各個瀏覽器,以及讓它能夠被更好地維護,而後作得更好(裝)看(B)一些。

高級選項

一味把<script>放到</body>前只會拔苗助長——你須要 「加載優化」 ;焦點圖沒有特別樣式不夠突出——你在想 「突出焦點」 ;須要給予用戶更多自主選擇——去看看 「位置指示」

加載優化(重要)

咱們會在頁面載入後看到輪播圖從第張轉到焦點 —— 很是有損體驗。可把一部分<script>放到<head>裏或輪播圖前,阻塞渲染。最好是提早計算 translateX 。

<div class="father" id="father" style="transform: translate3d(-3125px, 0px, 0px)">
</div>

而後刪去多餘初始移動代碼。

突出焦點

焦點 { 放大到110% }
其餘 { 半透明;正常大小 }
.focusing { opacity: 1;transform: scale3d(1.1, 1.1, 1) }
.father > div { opacity: 0.4;background: #bbb;transition: inherit; }

Lunbo.load(newId)backMove(newId)添加‘焦點樣式更改’行

class Lunbo {
  ...(先後文省略)
  load(newId) {
    ...
    this.photos[showingId].classList.remove("focusing");
    this.photos[newId].classList.add("focusing");
    ...
  }
  ...
  backMove(newId) {
    this.father.classList.add("moving");
    this.photos[newId].classList.add("focusing");
    this.father.style.transform = `translate3d(${this.countX(newId)}px, 0, 0)`;

    this.father.clientWidth;

    this.father.classList.remove("moving");
    this.photos[showingId].classList.remove("focusing");
    this.showingId = newId;
  }
  ...
}
<div class = "father" id="father" style="transform: translate3d(-3125px, 0px, 0px);">
  ...
  <div class="focusing">..</div><!--提早選擇焦點 -->
  ...
</div>

位置指示 & 切換

(在更新 ES6 以前,)這裏的代碼通過了測試。

1. 顯示

.seter {
  width: 400px;height: 20px;
  position: absolute;bottom: 0;left: calc(50% - 200px);
  cursor: pointer;
}
.seter > div {
  width: 80px;height: 28px;
  background: orange;
  float: left;
} .seter > .on { margin-top: -8px;transition: margin 0.5s ease-in-out; }
<div class="viewport" id="example">
  <div class="father" ...>
    ...
  </div>
  <div class="mother" id="left" left>左</div>
  <div class="mother" id="right" right>右</div>
  <div class="seter" id="seter">
    <div data-seter-id="0"></div>
    <div class="on" data-seter-id="1"></div>
    <div data-seter-id="2"></div>
    <div data-seter-id="3"></div>
    <div data-seter-id="4"></div>
  </div>
</div>
  • 函數 toSeterId 經過給予的圖片 Id 計算對應的 seterId;
class Lunbo {
  constructor(element) {
    ...
    this.seters = element.children[3].children;
    ...

    // 註冊移動事件
    ...
    element.children[3].addEventListener('click', function (event) {
      if (!event.target.hasAttribute('data-seter-id')) return;
      this.load(Number(event.target.getAttribute('data-seter-id')));
    }.bind(this))
  }
  ...
  load(newId) {
    ...
    this.seters[this.toSeterId(showingId)].className = '';
    this.seters[this.toSeterId(newId)].className = 'on';
    ...
  }
  ...
  toSeterId(id) {
    let seterId;
    if(id >= this.photosQuantity * 2) {
      seterId = id - 2 * this.photosQuantity;
    } else if(id >= this.photosQuantity) {
      seterId = id - this.photosQuantity;
    }
    return seterId;
  }
}

2. 可切換

  • 每次經過指示切換時先backMove至中間塊,後再進行移動;
  • 避免從第一張晃過中間數張至最後一張(最短路徑)。
// 繼上文 「顯示」 進一步更改
class Lunbo {
  constructor(element) {
    ...
    this.magicNumber = parseInt(this.photosQuantity / 2);
    ...
    // 註冊移動事件
    ...
    element.children[3].addEventListener('click', function (event) {
      if (!event.target.hasAttribute('data-seter-id')) return;

      const newId = Number(event.target.getAttribute('data-seter-id')) + this.photosQuantity;

      // 切換至中間塊
      this.backMove(toSeterId(showingId) + this.photosQuantity);

      // 最短路徑選擇
      if (newId > this.showingId + this.magicNumber) {
        // XXXX則移至左塊
        this.load(newId - this.photosQuantity);
      } else if (newId < this.showingId - this.magicNumber) {
        // XXXX則移至右塊
        this.load(newId + this.photosQuantity);
      } else {
        // 中間塊不變
        this.load(newId);
      }
    }.bind(this))
  }
  ...
}

(°_°ノ)

我忽然知道爲何越牛的大牛會越愈來愈牛了 !!!∑(゚Д゚ノ)ノ

其實他們原本是想寫一個文檔來講明,寫一個動態圖演示給新手的!('▽'〃)

可是……

作完後他們必定會腰痠背痛……(;`O´)o

// 本文再也不更新,除非做者開心

相關文章
相關標籤/搜索