原生JS輪播-各種效果的極簡實現


寒假持續摸魚中此為老早以前博客的重寫,當時還是分開寫的,這里匯總重寫,正好復習一遍

春招我來了!
所有有意思的,一股腦先扔進收藏,然后再也不看哈哈,真是糟糕。
今日事,今日畢,說起來容易。
當時竟然不是用markdown寫的!
當時使用var還需要解決必報的問題!而如今使用ES6的let,自帶領域的感覺就是不一樣!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    * {
      margin: 0;
      padding: 0;
    }
    ul, li {
      list-style: none;
    }
    body {
      background-color: #000;
    }
    .wrap {
      position: relative;
      border: 8px solid #fff;
      margin: 100px auto;
      width: 400px;
      height: 250px;
      overflow: hidden;
    }
    .pic-group {
      position: absolute;
      top: -250px;
    }
    .pic-group li img {
      display: block;
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <ul class="pic-group">
      <li><img src="./pic1.jpg" alt=""></li>
      <li><img src="./pic2.jpg" alt=""></li>
      <li><img src="./pic3.jpg" alt=""></li>
      <li><img src="./pic4.jpg" alt=""></li>
      <li><img src="./pic5.jpg" alt=""></li>
    </ul>
    <ol class="num-group">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
    </ol>
  </div>
</body>
</html>

一.輪播是什么?

輪播其實就是一個定時變換的廣告(卡片?圖片?)。
在HTML結構上他們其實是ul里面的li。數據層面來說的話,他們就是list的一條數據。

那么,這其中的變換其實也可以有很多種~你可能會知道現在的 vue-swiper這么絢麗的輪播效果

  • 滑動效果 --- position top位置的變化(意味着所有的item單/豎/橫向排列)
  • 緩沖效果 --- 透明度變化(意味着一開始所有的item剛開始都是疊在一起的)

二.變化和上述的代碼分析 --- 改變 top 則切換圖片(我們以滑動效果來開刀)。

如上面的代碼,只搭了個結構,我們看到wrap層被設為了 1.position:relative2.overflow:hidden,本質上是為了子層的pic-group服務的,我們設子層pic-group3.position:absolute,這樣的話 3 就可以根據 1和2 來達到 => 改變top位置(top位置為圖片高度的 n 倍哦!)

為了極簡!!!!!我這里只設置了 .pic-group li img 的寬高。一般情況下其實每個外層元素都會寫一個具體的高寬的。

  • 為什么我給.pic-group li img 設置 display:block ?

    實際上呢,img 它是行內元素。行內元素有默認的 4px 邊距。去除的辦法有 font-size:0等諸多方法,這里我追求代碼極簡。所以一句話,變成塊級元素吧少年。

  • 為什么我只給.pic-group li img 設置了 寬高?

    依舊為了極簡~實際上應該根據要求都設置一下寬高的。這里的結果就是
    a.內層 img 寬高繼承了最外的 400 250
    =>
    b.繼而作為 li 的子層撐開了 li
    =>
    c.li 再繼續撐開 .pic-group
    =>
    d.因為.pic-group的高度被老大的overflow:hidden限制了,最后就變成了一張圖片。
    原來的樣子應該是好幾層圖片~

三.變化的契機 --- 誰來改變 top?

我們給我們的輪播案例加上數字,通過點擊數字來按下改變世界的開關!
首先這是我們給數字添加的樣式

.num-group {
      position: absolute;
      bottom: 0;
      right: 0;
      height: 20px;
    }
    .num-group li {
      float: left;
      width: 20px;
      height: 20px;
      line-height: 20px;
      font-size: 10px;
      margin-right: 10px;
      background-color: #089e8a;
      color: #eee;
      text-align: center;
      border-radius: 10px;
      cursor: pointer;
      opacity: 0.8;
    }
    .num-group li:hover {
      box-shadow: 0 0 18px #000;
    }
    .num-group li.current {
      opacity: 1;
      color: #089a8a;
      background-color: #000;
    }

這里就不多解釋了,上面是是樣式的部分。
下面是JS的部分

// 首先,獲取我們要改變的元素
const oImage = document.getElementsByClassName("pic-group")[0];
// 其次,獲取改變世界的鑰匙
const oNumber = document.getElementsByClassName("num-group")[0];
const numList = oNumber.getElementsByTagName("li");
// 接下來,便是世界改變的邏輯
for (let i = 0; i < numList.length; i++) {
  // 關鍵:我們要把我們的 索引index 傳進 onclick/onmouseover 里面
  numList[i].onclick = () => {
    // numList[i].className = "current";
    clearNumState();
    numList[i].className = "current";
    const topValue = - i * 250;
    oImage.style.top = topValue + "px";
  }
}
// 每次新點擊 num,先置空所有list的current的類,此后再添加當前索引
const clearNumState = () => {
  for (const item of numList) {
    item.className = "";
  }
}
通過上面的代碼我們已經可以實現點擊數字按鈕進行對圖片的切換了.

改變世界的最終還是 JS,而不是CSS,我們用JS給數字們添加上事件。

四.用戶體驗為王! 下面就開始出現分歧了,如何來切換圖片呢?

  • 滑動的輪播

滑動輪播,所有的圖原先是排列着的。通過絕對定位來控制。同時外層設置overflow:hidden
top變化 -> 切圖。
* 這里的難點主要是我們要在當前的基礎top上滾動,並且滾動的效果要平滑。

// 首先,獲取我們要改變的元素
const oImage = document.getElementsByClassName("pic-group")[0];
// 其次,獲取改變世界的鑰匙
const oNumber = document.getElementsByClassName("num-group")[0];
const numList = oNumber.getElementsByTagName("li");
// 接下來,便是世界改變的邏輯
for (let i = 0; i < numList.length; i++) {
  // 關鍵:我們要把我們的 索引index 傳進 onclick/onmouseover 里面
  numList[i].onmouseover = () => {
    clearNumState();
    show(i);
  }
}
// 每次新點擊 num,先置空所有list的current的類,此后再添加當前索引
const clearNumState = () => {
  for (const item of numList) {
    item.className = "";
  }
}
// 滑動輪播,原則: 每次獲取當前位置,每次滾動的時間應該一致。(這是效果)
// 即是說,差的越遠,滾動越快。
// obj.offsetTop 指的是 obj 距離上方或者父級元素(position非static)的位置。
// 整型。單位像素。屬性為只讀!!只讀!!只讀!!
let timer = null;
const show = (index) => {
  if (timer) clearInterval(timer);
  const pre = oImage.offsetTop;
  let now = pre;
  const pos = -250 * index;
  const dis = pos - pre;
  const avg = dis / 10;
  timer = setInterval(() => {
    if (now === pos) {
      clearInterval(timer);
    } else {
      now += avg;
      console.log(now);
      oImage.style.top = now + "px";       
    }
  }, 20);
}

通過以上的代碼,我們基本能夠實現滑動輪播了,但是滑動的太僵硬了,沒有一種平時物體滑動的那種慣性。
簡單的來說,它是勻速運動的!
我們要讓它先 加速運動 -> 勻速運動 -> 減速運動.
So!修改代碼如下

let timer = null;
const show = (index) => {
  numList[index].className = "current";
  if (timer) clearInterval(timer);
  const target = -250 * index;
  let now = oImage.offsetTop;
  timer = setInterval(() => {
    if (now === target) {
      clearInterval(timer);
    } else {
      now += move(target);
      oImage.style.top = now + "px";
      console.log(oImage.offsetTop);
    }
  }, 20);
}
// 加速運動 -> 均速運動 -> 減速運動
const move = (target) => {
  // 當前值
  const now = oImage.offsetTop;
  const differ = target - now;
  console.log(differ / 10);
  const res = differ > 0 ? Math.ceil(differ / 10) : Math.floor(differ / 10);
  // 為什么呢?唉,基礎差導致這里還寫錯了。
  // 如果是 -0.1,我們要讓它為 -1,所以這里要用地板(返回的值更小)
  // -1 < -0.1
  // 如果是 0.1, 我們要讓它為 1,所以這里要用天花板(返回的值更大)
  // 0.1 < 1
  return res;
}

滑動輪播完整代碼

  • 緩沖的輪播

緩沖的輪播,其實就是漸隱效果的實現。這是一個透明度變化。
透明度變化 -> 切圖 -> 所有的圖原先都是在同一層的,就像疊起來的千層餅。
下面是CSS上的差別。
緩沖輪播的主要改動在這里,因為緩沖輪播是千層餅,所以我們不能讓它排列,讓它重疊!~

.pic-group li {
  position: absolute;
}
.pic-group li.current {
  opacity: 1;
}

在滑動中是下面這樣的,注意下面這句話要刪掉或是注視了。因為這里的absolute會影響到子級 liabsolute。(加個 li, 完事兒)

.pic-group {
  position: absolute;
}

然后下面是JS的一些差別,這個比較簡單。如果滑動的話,是只要控制 外層的 ul滾動就可以了
緩沖輪播的話則是要控制 imageList里面的每一張圖的透明度,所以這里多獲取一個東西。
const numList = oNumber.getElementsByTagName("li");

緩沖輪播完整代碼

五.重要知識點!!

時代在進步,刀耕火種的JS --- 談談重寫之前用var,重寫之后用let的變化
不用糾結閉包的問題了!!!!
哦草!
我他媽竟然忘了!

秀一秀古老的onclick

  for (var i = 0; i < oList.length; i++) {
    oList[i].onclick = function () {
      console.log(i);
    }
  }

這樣寫的后果是,無論怎么寫,最后的i的結果都會是oList.length
閉包(匿名函數)要使用外部作用於中變量的結果,這也是由於匿名函數本身無法傳遞參數,
故無法維護自己的作用域。
當函數調用外部變量就構成一個閉包,里面的變量會受到別的地方的影響。不可抗力!所以解決的方式是:構建一個只有匿名函數本身才可以訪問的閉包,保存只供本身使用的變量。

  • 古老的解決方案一.使用過去的 function 式函數,將 當前的 i 賦值給 index
  for (var i = 0; i < oList.length; i++) {
    oList[i].index = i;
    oList[i].onclick = function () {
      console.log(this.index);
    }
  }
  • 古老的解決方案二.使用匿名函數的方式隔離外部的i => 匿名函數:這就是我的領域,魔法-畫地為牢!!!
  for (var i = 0; i < oList.length; i++) {
    (function (i) {
      oList[i].onclick = () => {
        console.log(i);
      }
    })(i);
  }
  • 然而我是現代人,能坐車我干嘛要走路???So !
  for (let i = 0; i < oList.length; i++) {
    oList[i].onclick = () => {
      console.log(i);
    }
  }

正所謂四兩撥千斤!什么?你說沒差別? var -> let
歸根結底還是作用域的問題,let 是自帶領域的。

六.相關知識點- Math.floor()、Math.ceil()、Math.round

我們在滑動輪播中為了達到距離越遠滑動越快的效果。
我們的解決方案:
令每一次的 interval 時間一致。
改變每一次的移動距離來實現。
每次的移動距離由 當前offsetTop 和 目的地 決定。
每次的移動距離都有最小值。
同時怕計算得出的值相加會超出原先設定的 目的地。
所以使用到了上面的兩個函數。(目的地和當前的offsetTop的差可正可負).

  • Math.floor 地板:返回的永遠小於等於原來的(下限)。
const a = 0.1; // 0      0 < 0.1
const b = -a;  // -1     -1 < -0.1
const c = 0.6; // 0
const d = -c;  // -1
  • Math.ceil 天花板:返回的永遠大於等於原來的(上限)。
const a = 0.1; // 1
const b = -a;  // 0
const c = 0.6; // 1
const d = -c;  // 0
  • Math.round 返回最近的。
const a = 0.1; // 0 四舍
const b = -a;  // -0  依舊四舍
const c = 0.6; // 1 五入
const d = -c;  // -1 五入

當我們的移動距離為 -0.1 的時候,用 Math.ceil 意味着它的 top 永遠為0了QAQ.
當我們的移動距離為 0.1 的時候,用 Math.floor 也以為這 移動top 為0.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM