转载

新货!如何制作一个高效轮播图?

新货!如何制作一个高效轮播图?

轮播图千种万种,怎样才能做出符合要求的轮播图?原理上天入地,如何优化才能达到极限丝滑?本文作者将解答这一切,通过现场制作一个轮播图,带你详细了解、理解,制作 All kinds of 高性能轮播图 !

本文共分 首尾相接地轮播、高级选项[加载优化, 突出焦点, 有限轮播, 位置指示] 两个板块。你可以在高级选项 中为自己的轮播设置添加 单独的特性和功能。尽管用, 所有的 ,所有的,所有的(重要的事说三遍),作者都已竭尽全力做到高效!

另外,在事实上,轮播图的点击率通常都很低,很少能引起用户的注意,而却往往占用了页面某个极重要的位置。你的网站 真的 需要一个轮播图吗?轻轻问自己三声,谷歌一下对轮播图效果的相关调查和建议,再决定是否要着手制作你的轮播图。

首尾相接地轮播~

这里原理非常巧妙,是作者在Google Play 找到的方法。如果你不需要这种可以单方向无限轮播的图,那么可以在了解大概后去 ↓ 高级选项 中找到 有限轮播 进行相关更改。

1. 包裹图片

性能不允许我们为每一张图片计算位置,同样我们并没有这个必要。我们要一个父元素 div.father 来含括,用 .father > div 对图片设置外观。 div.viewport 作为可见部分,是爸爸的爸爸,帮他儿子隐藏不出场的图片。嗯,就像这样

.viewport {
  width: 900px;height: 300px; /* 可视宽,图片高 */
  overflow: hidden; /* 超出隐藏 */
  position: relative /* 为下文妈妈的指导位置作准备 */
}
.father {
  height: inherit; /* 图片高 */
  width: 3000%; /* 为了大,子元素 float 无法将其撑开 */
  transform: translate3d(0, 0, 0);transition: transform 0.5s ease-in-out /* 丝滑流畅 */
}
.father > div {
  width: 550px;height: inherit; /* 自定义 */
  float: left /* 排起来 */
}

这里面的丝滑流畅是因为 transform: translate3d() 为元素开启了GPU 加速。欲知详情,请谷歌。

2. 方向规定

.fathertransform: translate3d() 帮儿子们完成整体移动。定时调整位置是不被推荐的,不让用户切换是不可能的。要 .mother 作为爸爸的方向标,她指导爸爸。

.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}
<div class="viewport">
  <div class="father" id="father">
    <div>A</div><!-- 1 -->
    <div>B</div>
    <div>C</div><!-- 3 -->
    <div>D</div>
    <div>E</div><!-- 5 -->
  </div>
  <div class="mother" id="left" left>^_^</div>
  <div class="mother" id="right" right>:-)</div>
  <!-- 我可没说有两 wife...只是她指导老爸一会儿向东,一会儿向西,抓不着方向 ~~ -->
</div>

3. 重要的 JS

var showingId; //缓存轮播的焦点。从0 开始,和JS 的下标号[] 一样
var fatherGo=to=>{} // Mom's order.
document.getElementById("left").onclick= ()=>{fatherGo(showingId-1)}
document.getElementById("right").onclick= ()=>{fatherGo(showingId+1)}
  • 页面加载时:选取一张作为焦点

    切换时: fatherGo(to) 将负责跳转到指定的焦点图;

  • 高效、首尾相接地轮播( 有限轮播 见 ↓ 高级选项)

这两个是思路,也是 题点 。对第一题,可以这样解决:

var father=document.getElementById("father"),
  sons=father.children,
  width=550, //自己设的CSS, 如果图有margin, 要加上(如 margin: 0 20 则 550+20*2)
  outerWidth=(900-width)/2, // (可视宽-焦点宽)/2,好理解吧,只显示了一部分的图片宽度
  showingId=parseInt(sons.length/2)-1; //除法求整数部分,并保证对应JS 中代号
var askX = id => -id*width+outerWidth;
var fatherGo = to => {
  father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
  showingId=to
}
fatherGo(showingId)

要把Id 为2 的儿子作焦点,即第3 个儿子,我们可以向左挪过去2 个儿子(这时候 儿子.Id=2 就靠在了最左边),再加回 outerWidth —— ask(id) 函数原理。

第二点。作两个儿子们的分身,在移动到 差点到 首尾连接处时 关闭动画 挪到另一个分身相应位置~…

<style>
.moving {transition: none} /* 关闭动画样式 */
</style>
<div class="father" id="father">
  <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>

  <div>A</div><!-- 1 -->
  <div>B</div>
  <div>C</div><!-- 3 -->
  <div>D</div>
  <div>E</div><!-- 5 -->

  <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>
</div>
//上面的代码
var father=document.getElementById("father"),
  sons=father.children,
  width=550,
  outerWidth=(900-width)/2,
  showingId=parseInt(sons.length/2)-1; //不变
var askX = id => -id*width+outerWidth;
var fatherGo = to => {
  father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
  showingId=to
}
fatherGo(showingId)

//此步的更改 - 开动你的大脑!!
var closeLeft=1, closeRight=sons.length-2,
  //这两变量表示差一点到 “首尾相接处” 的图片Id。忽略拙略的命名
  //Left: 第2 张图,Id 为1; Right: 倒数第二张,Id 为总数-2
  toLeft=sons.length/3*2+1, toRight=sons.length/3-2,
  //这两表示当运动到上面两个Id 图片时应悄悄跳转到的分身Id
  time=new Date();//防止多次跳转造成下面setTimeout 混乱

fatherGo = to => {

  var newDate=new Date()
  if(newDate-time<600)return;
  time=newDate
  //↑ 防止多次跳转造成下面setTimeout 混乱

  father.style.transform=`translate3d(${askX(to)}px, 0, 0)`;
  showingId=to

  if(to==closeLeft){to=toLeft} else if(to==closeRight){to=toRight}
  else {return}
  setTimeout(()=>{
    father.classList.add("moving")
    setTimeout(()=>{
      father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
      setTimeout(()=>{
        father.classList.remove("moving")
      },50);
    },30)
  },500);//translation 的时间

  showingId=to
}

4. 整理代码

<!DOCTYPE html><html>
<head><title>轮播图~~ 转呀转</title>
<style>
html,body {height: 100%}
.viewport {
  width: 900px;height: 300px; /* 可视宽,图片高 */
  overflow: hidden; /* 超出隐藏 */
  position: relative /* 为下文妈妈的指导位置作准备 */
}
.father {
  height: inherit; /* 图片高 */
  width: 3000%; /* 为了大,子元素 float 无法将其撑开 */
  transform: translate3d(0, 0, 0);transition: transform 0.5s ease-in-out /* 丝滑流畅 */
}
.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}
.moving {transition: none}
</style>
</head>
<body>
<div class="viewport">
  <div class="father" id="father">
    <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>

    <div>A</div><!-- 1 -->
    <div>B</div>
    <div>C</div><!-- 3 -->
    <div>D</div>
    <div>E</div><!-- 5 -->

    <div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>
  </div>
  <div class="mother" id="left" left>^_^</div>
  <div class="mother" id="right" right>:-)</div>
</div>
<script>
var father=document.getElementById("father"),
  sons=father.children,
  width=550,outerWidth=(900-width)/2,
  showingId=parseInt(sons.length/2)-1,
  askX = id => -id*width+outerWidth,
  closeLeft=1, closeRight=sons.length-2,
  toLeft=sons.length/3*2+1, toRight=sons.length/3-2,
  time=new Date();
father.style.transform=`translate3d(${askX(showingId)}px, 0, 0)` //加载页面时移动

var fatherGo = to => {

  var newDate=new Date()
  if(newDate-time<600)return;
  time=newDate

  father.style.transform=`translate3d(${askX(to)}px, 0, 0)`;
  showingId=to

  if(to==closeLeft){to=toLeft} else if(to==closeRight){to=toRight}
  else {return}
  setTimeout(()=>{
    father.classList.add("moving")
    setTimeout(()=>{
      father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
      setTimeout(()=>{
        father.classList.remove("moving")
      },50);
    },30)
  },500);//translation 的时间

  showingId=to
}
document.getElementById("left").onclick= ()=>{fatherGo(showingId-1)}
document.getElementById("right").onclick= ()=>{fatherGo(showingId+1)}
</script>
</body></html>

代码通过了测试。你可能需要码更多的代码,以兼容各个浏览器。

高级选项

激动人心 的地方终于到了!

一味地把 <script> 放到 </body> 前只会 适得其反 ——你需要 “加载优化” ;焦点图没有 特别样式 不够突出——你在想 “突出焦点” ;上级的要求不能 没头没尾 ——去看看 “有限轮播” ……所有的所有,尽在这里找到!

加载优化 - 重要

一味地把 <script> 放到 </body> 前只会适得其反。我们会在页面载入后看到轮播图转到焦点——这是非常有损体验的。其实可以把 一部分 <script> 放到 <head> 里面,或者轮播图前,阻塞DOM 的渲染。最有效的是提前计算好translateX 值,放到 style=""

提前计算:

<div class="father" id="father" style="transform: translate3d(-3125px, 0px, 0px);">...</div>
<!-- -3125: 自己用前面的AskX 函数算 -->

最后删去JS 中多余的加载页面时移动 代码。

为了世界和平,后面的都这样优化了。

突出焦点

如何要用户Focus on 你的焦点?

/* 我们想要一个这样的效果 */
焦点 {放大到110% }
其他 {半透明;正常大小}

没什么不对的。

.focusing {opacity: 1;transform: scale3d(1.1, 1.1, 1)/*3D 用于GPU 加速 */}
.father > div {opacity: 0.4;background: #bbb;transition: transform 0.6s ease-in-out}
.father.moving > div {transition: none}

为了不让用户在 “转换分身” 时看出来,得添加上面的第三个CSS。而JS 中,我们需要为 fatherGo(to) 函数添加更改焦点的方法,HTML 中提前为中心焦点Foucs 啦啦啦

fatherGo = to => {
  var newDate=new Date()
  if(newDate-time<600)return;
  time=newDate

  father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
  sons[showingId].classList.remove("focusing")
  sons[to].classList.add("focusing")
  showingId=to

  if(to==closeLeft){to=toLeft} else if(to==closeRight){to=toRight}
  else {return}
  setTimeout(()=>{
    father.classList.add("moving")
    sons[to].className="focusing"//添加的代码
    setTimeout(()=>{
      father.style.transform=`translate3d(${askX(to)}px, 0, 0)`
      sons[showingId].className=""//添加的还有这
      setTimeout(()=>{
        father.classList.remove("moving")
      },50);
    },30)
  },500);
  showingId=to
}
<div class="father" id="father" style="transform: translate3d(-3125px, 0px, 0px);">
  ...
  <div class="focusing">..</div><!--提前算好的焦点(可参考使用前面showingId 算法 -->
  ...
</div>

有限轮播

这么简单的轮播,没压力啊。下面是总的HTML ,注释表示变化

<!DOCTYPE html><html>
<head><title>轮播图~~ 转呀转</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.5s ease-in-out
}
.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}
.moving {transition: none}
</style>
</head>
<body>
<div class="viewport">
  <div class="father" id="father" style="transform: translate3d(-375px, 0px, 0px);">
    <div>A</div><!-- 1 -->
    <div>B</div>
    <div>C</div><!-- 3 -->
    <div>D</div>
    <div>E</div><!-- 5 -->
  </div>
  <div class="mother" id="left" left>^_^</div>
  <div class="mother" id="right" right>:-)</div>
</div>
<script>
var father=document.getElementById("father"),
  sons=father.children,
  width=550,outerWidth=(900-width)/2,
  showingId=parseInt(sons.length/2)-1,
  askX = id => -id*width+outerWidth,
  closeLeft=0, closeRight=sons.length-1,//这两变量被改了。删掉了toLeft 以及toRight
  left=document.getElementById('left'), right=document.getElementById("right");//缓存DOM

var fatherGo = to => {

  if(to==closeLeft-1 || to==closeRight+1)return;//添加了超出有限范围的判断

  //不再需要避免连击(因为没有了setTimeout)

  if(to==closeLeft){
    father.style.transform="translate3d(0, 0, 0)";
    left.classList.add("aClass")//自己看要不要给已经不能滑动的.mother 特殊样式
  }
  else if(to==closeRight){
    father.style.transform=`translate3d(${-closeRight*width+2*outerWidth}px, 0, 0)`;
    right.classList.add("aClass")//同上。给不给已经不能滑动的.mother 特殊样式
  }
  else{
    father.style.transform=`translate3d(${askX(to)}px, 0, 0)`;
    left.classList.remove("aClass");right.classList.remove("someClass")//你懂删不删
  }
  //有限判断

  //sons[showingId].className=""
  //sons[to].className="yourClass"
  //如果你需要 “突出焦点” ,还原这里的代码

  showingId=to

  //删去无用代码
}
left.onclick= ()=>{fatherGo(showingId-1)}//缓存了变量,可直接绑定
right.onclick= ()=>{fatherGo(showingId+1)}
</script>
</body></html>

位置指示

需与 “有限滚动” 依存。总的HTML 在这

<!DOCTYPE html><html>
<head><title>轮播图~~ 转呀转</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.5s 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.4);
  position: absolute;top: 0;
}
.mother[left] {left: 0} .mother[right] {right: 0}
.mother.close {opacity: 0.3;transition: opacity 0.6s ease}
.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 > div.on {margin-top: -8px;transition: margin 0.5s ease-in-out}
</style>
</head>
<body>
<div class="viewport">
  <div class="father" id="father" style="transform: translate3d(-375px, 0px, 0px);">
    <div>A</div><!-- 1 -->
    <div>B</div>
    <div>C</div><!-- 3 -->
    <div>D</div>
    <div>E</div><!-- 5 -->
  </div>
  <div class="seter" id="seter">
    <div id="0"></div>
    <div class="on" id="1"></div>
    <div id="2"></div>
    <div id="3"></div>
    <div id="4"></div>
  </div>
  <div class="mother" id="left" left>^_^</div>
  <div class="mother" id="right" right>:-)</div>
</div>
<script>
var father=document.getElementById("father"),
  sons=father.children,
  width=550,outerWidth=(900-width)/2,
  showingId=parseInt(sons.length/2)-1,
  askX = id => -id*width+outerWidth,
  closeLeft=0, closeRight=sons.length-1,
  left=document.getElementById('left'), right=document.getElementById("right"),
  seter=document.getElementById("seter"),
  seters=seter.children;

var fatherGo = to => {

  if(to==closeLeft-1 || to==closeRight+1)return;

  if(to==closeLeft){
    father.style.transform="translate3d(0, 0, 0)";
    left.classList.add("close")
  }
  else if(to==closeRight){
    father.style.transform=`translate3d(${-closeRight*width+2*outerWidth}px, 0, 0)`;
    right.classList.add("close")
  }
  else{
    father.style.transform=`translate3d(${askX(to)}px, 0, 0)`;
    left.classList.remove("close");right.classList.remove("close")
  }

  seters[showingId].className=""
  seters[to].className="on"
  //sons[showingId].className=""
  //sons[to].className="yourClass"
  //如果你需要 “突出焦点” ,还原这里的代码

  showingId=to
}
left.onclick= ()=>{fatherGo(showingId-1)}
right.onclick= ()=>{fatherGo(showingId+1)}

seter.onclick= e=>{
  fatherGo(Number(e.target.id))
}
</script>
</body></html>

之前发了一次。不知什么问题,文章可以显示在列表中,但除作者外无法访问?累(luì) 、、。如果任何不足缺少,评论建议!谢

原文  https://segmentfault.com/a/1190000008255568
正文到此结束
Loading...