原生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;
       400px;
      height: 250px;
      overflow: hidden;
    }
    .pic-group {
      position: absolute;
      top: -250px;
    }
    .pic-group li img {
      display: block;
       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;
       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.

原文地址:https://www.cnblogs.com/can-i-do/p/8443021.html