我们经常可以在网页上看到轮播图的效果,这是一个很常见的应用,但是,要想比较完美地实现这个功能,还是需要花点时间的。
要实现的功能
首先我们来看看要实现这么一个轮播图需要哪些功能,这里我把我想到的都列出来了。
- 页面加载后轮播图自动开始播放,每张图片停几秒钟。
- 图片与图片之间实现平滑过渡动画效果,不显突兀。
- 鼠标悬停到当前图片时轮播动画停止,鼠标离开图片后继续开始轮播。
- 图片上有左右翻页功能按钮,点击左边按钮图片往左滑动,点击右边按钮图片右滑。
- 图片下端有显示图片个数的小圆点,当前图片是第几个,则第几个小圆点“点亮”。
- 离开当前页面后轮播动画停止,回到当前页面后轮播动画继续。
我能想到的要实现的功能就这么多,接下来就一步步开始实现。
HTMl结构
先不管JS、CSS部分,我们先把整体结构定下来。这里直接贴上主体部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <div class="wrap"> <div class="loop-container"> <img src="./assets/cat5.jpg" alt="5" class="loop-image"> <img src="./assets/cat1.jpg" alt="1" class="loop-image"> <img src="./assets/cat2.jpg" alt="2" class="loop-image"> <img src="./assets/cat3.jpg" alt="3" class="loop-image"> <img src="./assets/cat4.jpg" alt="4" class="loop-image"> <img src="./assets/cat5.jpg" alt="5" class="loop-image"> <img src="./assets/cat1.jpg" alt="5" class="loop-image"> </div> <div class="buttons"> <span class="on">1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div>
<div class="arrow arrow-left"> <div class="pt"> <span class="pt-inner"></span> </div> </div>
<div class="arrow arrow-right"> <div class="pt"> <span class="pt-inner"></span> </div> </div> </div>
|
这里要强调的一点就是,虽然最后显示只有5张图片,但我插入了7个img
标签,其中第一个和最后一张图一样,最后一个和第一个图一样。为什么要这么做呢?是因为要实现平滑的动画过渡效果,后面JS部分会提到。
CSS样式
接下来就是CSS设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| .wrap { display: flex; justify-content: center; align-items: center; position: absolute; padding: 0; margin: 0; width: 600px; height: 500px; top: 50%; left: 50%; transform: translate(-50%, -50%); overflow: hidden; } .wrap .loop-container { position: absolute; left: -600px; top: 0; width: 700%; height: 100%; margin: 0; padding: 0; animation: left .6s ease-out; font-size: 0; } .wrap:hover > .arrow { display: block; }
.loop-container .loop-image { width: 600px; height: 500px; margin: 0; }
|
这里我没有把全部的CSS代码贴出来,完整的代码我会在最后给出。
我只说几个要注意的地方:
- 首先外部容器要设置
overflow:hidden
,这样才能把多余的图片遮住;
- 其次由于img默认是inline元素,显示出来的特性是inline-block,所以img之间会有4px的空隙,即使设置了margin和padding为0也不能消除。为此我困惑了很久。。。后来上网找资料才发现解决方案。一般有几种解决方案,这里我用的是设置父元素的font-size为0,然后img的font-size设不设置根据需要,这样就可以消除空隙。更详尽的解决方案可以参考张大神的博客
其他css设置就根据样式慢慢调整了。
JS部分
接下来就是重头戏,JS的实现了。我们一步一步来看。
我先创建了一个整体的“类”,里面有一些所需要用到的方法和属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| function Marquee() { this.timer = 0; this.index = 0; }
Marquee.prototype.animate = function (aimLeft) { var curLeft = parseInt(loopContainer.style.left) || -600, speed = (aimLeft - curLeft) / 20, delay = 20, self = this; var time = setInterval(function () { curLeft += speed; loopContainer.style.left = curLeft + 'px'; if (curLeft === aimLeft) { clearInterval(time); if (aimLeft <= -3600) { loopContainer.style.left = '-600px'; } if (aimLeft >= 0) { loopContainer.style.left = '-3000px'; } self.showCurrentDot(); } }, delay); }; Marquee.prototype.showCurrentDot = function () { var dots = document.getElementsByTagName('span'); for (var i = 0, len = dots.length; i < len; i++) { dots[i].className = ''; } dots[this.index].className = 'on'; };
Marquee.prototype.changePhoto = function (offset) { var left = loopContainer.style.left, newleft = left ? parseInt(left) + offset : offset - 600; this.index = offset > 0 ? this.index - 1 : this.index + 1; if (this.index > 4) { this.index = 0; } if (this.index < 0) { this.index = 4; } this.animate(newleft); };
Marquee.prototype.gotoPhoto = function (count) { var newleft = count * -600; this.index = count - 1; this.animate(newleft); }
var maq = new Marquee();
|
自动播放图片
轮播图,顾名思义就是轮流播放图片,所以首先要实现的功能就是自动轮流循环播放图片。 有了之前的类,我们要做的就是当页面加载完毕后开始循环播放:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| window.addEventListener('load', function() { var prevBtn = document.getElementsByClassName('arrow-left')[0], nextBtn = document.getElementsByClassName('arrow-right')[0], loopContainer = document.getElementsByClassName('loop-container')[0], btns = document.getElementsByClassName('buttons')[0], wrap = document.getElementsByClassName('wrap')[0]; var stopFlag = 0;
function startInterval() { maq.timer = setTimeout(function () { if (!stopFlag) { maq.changePhoto(-600); startInterval(); } else { clearTimeout(maq.timer); } }, 4500); } startInterval(); });
|
这里我循环动画没有用setInterval函数,而是用的setTimeout。因为setInterval的机制是每隔一段时间就把事件加入到队列中去,但如果之前的事件还没执行完,就会容易造成队列堵塞。比如说setInterval里的函数执行时间要4秒,如果setInterval的间隔时间少于4秒,则会造成队列里的事件越来越多,而之前的事件却没执行完,这样可能就会使队列里的事件堵塞,最后一次性全部执行,而没有达到预期的间隔效果。
所以我用setTimeout来代替setInterval,每次要加入新的事件之前都先判断一下stopFlag
是否为0。stopFlag
的作用就是记录是否要停止动画,为0则不停止,为1则停止。
这里记住要clearTimeout,目的是把已经在队列里但还没有执行的事件清除,这样可以达到立即停止动画的效果。
鼠标悬停停止轮播动画,离开后开始动画
监听mouseover和mouseout事件来达到目的:
1 2 3 4 5 6 7 8
| wrap.addEventListener('mouseover', function () { stopFlag = 1; clearTimeout(maq.timer); }); wrap.addEventListener('mouseout', function () { stopFlag = 0; startInterval(); });
|
左右切换图片
通过点击左右箭头按钮实现图片之间的滚动切换:
1 2 3 4 5 6
| prevBtn.addEventListener('click', function() { maq.changePhoto(600); }); nextBtn.addEventListener('click', function () { maq.changePhoto(-600); });
|
点击小圆点跳转到对应图片
这里我用了事件代理,不用在每个小圆点上绑定click事件,提高dom性能:
1 2 3 4 5 6
| btns.addEventListener('click', function (event) { var count = parseInt(event.target.innerText); if (count < 6) { maq.gotoPhoto(count); } });
|
离开当前页面动画停止
这里我开始没有想到,后来是当我每次切换到别的页面后再回到当前页面,发现动画效果出现问题了。经过一番debug才发现是因为chrome浏览器设置了离开当前页面后setInterval继续执行,如果setInterval的间隔时间小于100ms,则按100ms来执行,于是回来时动画的时间就发生错误了。
所以我们需要设置离开页面时动画停止,这样也节省了不少性能。这里利用的是onvisibilitychange
事件:
1 2 3 4 5 6 7 8 9
| document.addEventListener('visibilitychange', function () { if (document.hidden) { stopFlag = 1; clearTimeout(maq.timer); } else { stopFlag = 0; startInterval(); } });
|
这里可以直接调用document.hidden
API判断当前页面是否被隐藏,如果document.hidden为true则代表已经切换到别的页面,于是设置stopFlag
为1,使动画停止。也可以用document.visibilityState
,如果不为’visible’,则代表离开了当前页面。
这里其实可以不用setTimeout、setInterval来实现动画,而是用requestAnimationFrame
,后者的优点是自动以浏览器支持的最小刷新间隔来实现重绘,使性能得到最大化提升,而且实现了离开页面重绘停止,大大节省性能。这里我就没有实现了,要想了解requestAnimationFrame,可参考资料。
总结
通过上述方法,最后实现了一个比较完善的轮播图,全部的动画效果都是用JS来实现的。其实要想实现自动轮播,也可以用CSS3的animation来实现,而且实现起来更快,这里我就不阐述了。
最后贴出实现的demo。
本文标题:用原生JS写轮播图
文章作者:flyrk
发布时间:2017-08-27
最后更新:2022-02-25
原始链接:https://flyrk.github.io/2017/08/27/how-to-write-js-marquee/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!