本博客参照了此文章(https://www.cnblogs.com/LIUYANZUO/p/5679753.html)
效果如下:
1 自动播放
2 点击箭头切换
3 点击圆点切换
DOM
<div id="container" class="container">
<div class="pics">
<img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2193651974,4181473948&fm=26&gp=0.jpg" alt="">
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3057381651,2463402979&fm=26&gp=0.jpg" alt="">
<img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=379963434,28431380&fm=26&gp=0.jpg" alt="">
</div>
<div class="buttons">
<div class="button on"></div>
<div class="button"></div>
<div class="button"></div>
</div>
<a href="javascript:;" id="prev" class="arrow"><</a>
<a href="javascript:;" id="next" class="arrow">></a>
</div>
CSS
<style>
img{
height: 400px;
width: 600px;
}
.container{
position: relative;
height: 400px;
width: 600px;
margin-right: 0;
overflow: hidden;
}
.pics{
z-index: 1;
position: absolute;
width: 1800px;
height: 400px;
}
.pics img{
float: left;
}
.arrow {
position: absolute;
z-index: 99;
font-size: 36px;
top: 180px;
z-index: 2;
display: none;
font-weight: bold;
line-height: 39px;
text-align: center;
width: 40px;
height: 40px;
color: #fff;
background-color: RGBA(0, 0, 0, .3);
cursor: pointer;
}
#prev {
left: 10px;
}
#next {
left: 550px;
}
.buttons{
z-index: 99;
position: absolute;
bottom: 10px;
left: 45%;
}
.button{
display:inline-block;
height: 20px;
width: 20px;
border-radius:50px;
color:black;
background:RGBA(0, 0, 0, .3);
}
.on{
background: pink;
}
</style>
JS
<script>
const container = document.getElementById('container')
const pics = document.getElementsByClassName('pics')[0];
const prev = document.getElementById('prev');
const next = document.getElementById('next');
const buttons = document.getElementsByClassName('button');
let index = 0;
// button相关
for (let index = 0; index < buttons.length; index++) {
buttons[index].addEventListener('click', function(){buttonsShow(index)},false);
}
function buttonsShow(_index = index) {
for (var i = 0; i < buttons.length; i++) {
if (Array.from(buttons[i].classList).includes('on')) {
buttons[i].classList.remove('on');
}
}
buttons[_index].classList.add('on');
animate(_index*(-600), false);
}
//播放相关
function play() {
timer = setInterval(function(){
// animate(-600, true);
next.onclick();
}, 2000);
}
function stop(){
clearInterval(timer);
}
//动画相关
function animate(offset, isRelative) {
if (isRelative) {
if (!pics.style.left) {
var oldoffset = 0;
var newoffset = oldoffset + offset;
} else {
var newoffset = parseInt(pics.style.left) + offset;
}
pics.style.left = newoffset + 'px';
if (newoffset == 600) {
pics.style.left = -1200 + 'px';
}
if (newoffset == -1800) {
pics.style.left = 0 + 'px';
}
} else {
pics.style.left = offset + 'px';
}
}
//前进后退键相关
prev.onclick = function() {
animate(600, true);
if(index == 0) {
index = 2;
} else {
index = index - 1;
}
buttonsShow();
}
next.onclick = function() {
animate(-600, true);
if(index == 2) {
index = 0;
} else {
index = index + 1;
}
buttonsShow();
}
container.addEventListener('mouseover',function () {
console.log('in');
prev.style.display = 'block';
next.style.display = 'block';
stop();
},true)
container.addEventListener('mouseout',function () {
prev.style.display = 'none';
next.style.display = 'none';
play();
},true)
play();
// container.onmouseover = function(){
// stop();
// };
// // container.onmouseover = stop;或者这么写也可以!
// container.onmouseleave = function(){
// play();
// }
// registerEvent(container, 'onmousemove');
// registerEvent(container, 'onmouseover');
// function registerEvent(target, event) {
// target.addEventListener(event, function () {
// // console.log('ss');
// prev.style.display = 'block';
// next.style.display = 'block';
// }, false)}
</script>
所遇到的坑:
1.方法的声明和调用
1⃣️ 一开始我是这么写的,但是发现点击前进后退按钮没有效果,在annimate函数里面console.log之后发现只有刷新打开页面的时候打印了,之后的点击并没有打印,说明没有进animate函数里面。
prev.onclick = animate(600);
next.onclick = animate(-600);
2⃣️ 修改成下面代码这样之后就可以了。我这个问题错在混淆了声明和调用的定义,onclick后面要赋值的是一个函数,当click的时候我们会去调用这个函数,而不是赋值一个函数结果!!
js文件在加载的时候,会自顶向下自动运行一遍,如果如上代码一样,prev.onclick得到的是animate函数执行的结果,也就是null;修改之后,我们吧animate放在一个函数里面,这个函数的执行结果是animate函数,这样我们onclick才能正常调用该函数。
prev.onclick = function() {
animate(600);
}
next.onclick = function() {
animate(-600);
}
3⃣️ 还有第三种写法,并非一定要用一个function包起来,我们可以把不带括号的animate赋给onclick事件,这样onclick得到的是animate这个函数,而不是执行结果,因为‘()‘表示执行!,但是这样有个弊端,就是不能传参。
prev.onclick = animate;
next.onclick = animate;
2. 块级作用域和闭包
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function(){buttonsShow(i)},false);
}
上面这段代码的目的是给每个小圆点添加一个click的监听,点击之后能够切换到相应的图片。但是我们发现,这么写点击之后没有任何效果。我们在buttonShow函数里面添加一个console,打印传过来的值,结果发现不管点击哪个,打印出的值永远是3.
原因是这样的,ES5里面是没有块级作用域的,在高级程序设计第三版中76页,会看到这么一句话:
“对javascript来说,由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。”
因为我们在for循环里面定义的计数参数i,因为没有块级作用域,所以是一个全局变量,说明它并不会消失且保持最后的执行结果,我这里又3张图片,所以按理来说i=2,但是因为最后一次执行完之后还会执行一次i++,所以i最后在全局保持值为3。所以!!三个小圆点不管如何点击,传递的参数始终为3。(还不明白同学建议看一下高级程序设计3 第七章)。
ps:报错是因为最后一次执行的i++让i最后加到了3,超出了图片的index上限-----2,所以报错。
1⃣️这种问题相信大家遇到过很多次,以前ES6没有出来的时候,只有一种解决办法,那就是立即执行函数仿块级作用域。如下所示。
(function(){
......
})();
之前看高级程序三,感觉解释的并不是特别清楚。我对仿块级作用域的理解是这样的,虽然像if for循环这样的语句没有块级作用域,但是函数是有块级作用域的,在函数里面定义的变量,在函数外取不到并且函数执行完毕之后,变量会被销毁,仿块级作用域正是利用了这个特点来模仿 块级作用域。
我们把需要块级的地方用匿名函数进行模拟,并添加一对圆括号,对该函数进行立即执行,这样保证了变量能够及时销毁。加圆括号同时也是因为 如果单单只是写一个没有名字的匿名函数,浏览器会认为你在申明一个函数,但是这个函数却没有名字,因此会报错。
通过仿块级作用域,代码修改如下:
for (var i = 0; i < buttons.length; i++) {
// 这里使用的是立即执行函数,
(function(i) {
buttons[i].addEventListener('click', function(){buttonsShow(i)},false);
})(i)
}
2⃣️ES6出来之后,想想其最著名的一条是什么?那就是块级作用域!
我们单单只需要把计数参数的申明var变成let即可!!这样for循环里面就是一个块级作用域!多么简单啊,不能想象没有ES6的生活。。。。
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function(){buttonsShow(i)},false);
}
3.事件冒泡和事件捕获
小心得
1.函数默认参数
function buttonsShow(_index = index) {
console.log(_index);
for (let i = 0; i < buttons.length; i++) {
if (Array.from(buttons[i].classList).includes('on')) {
buttons[i].classList.remove('on');
}
}
buttons[_index].classList.add('on');
animate(_index*(-600), false);
buttonShow函数有两处地方用到,一是轮播/点击前进后退的时候,二是点击确定小圆点的时候;这两种情况肯定都要向函数传index才能改变相应的小圆点的样式。但是第一种情况的index是不确定的,是一个相对值不是一个绝对值,每次要么+1,要么-1;第二种情况点击小圆点得到的index是一个绝对值index,我们能知道具体点击的是第几个小圆点。
我的解决办法是全局设置一个index,在左右切换的时候对这个全局index进行改变,这样我们也无需给buttonShow传值了,因为index是全局的,所有函数都可以调用。然而这样会有个小毛病,那就是buttonShow函数怎么知道是使用第一种情况的全局index还是使用第二种情况传入的index?
这里用到了函数的默认参数,因为两种情况一个传值了一个没有传值,所以第一种情况没有传值的时候就用默认参数---全局的index,第二种情况传值具体的index就把默认参数给覆盖了。
2.函数复用/功能抽离
相似的,
Animate函数目的是用于改变小圆点的样式。逻辑里面一共有两处需要用到这个函数,第一个是点击确定小圆点切换图片,第二个是自动播放和点击前进后退按钮时候切换图片。
一开始我想到都是同样的功能,那就统一使用animate函数比较好,但是这两处有一点点不一样。第一处是点击确定的小圆点,得到一个绝对值index;第二处是每次给pics左右加减600px来切换图片,给animate的传参是一个相对值,不是一个绝对值。这样导致一个比较尴尬的事情,animate函数一开始是设定为接受相对值的
那我们点击小圆点得到的绝对值index怎么转换为相对值?
我的解决办法是,给aniamte函数多加一个参数isRelative,true的情况就是简单的轮播/点击前进后退,第一个参数offset传如的是一个相对值600px;false就是点击小圆点切换图片情况,第一个参数offset传入的是图片的绝对定位值。
function animate(offset, isRelative) {
if (isRelative) {
if (!pics.style.left) {
var oldoffset = 0;
var newoffset = oldoffset + offset;
} else {
var newoffset = parseInt(pics.style.left) + offset;
}
pics.style.left = newoffset + 'px';
if (newoffset == 600) {
pics.style.left = -1200 + 'px';
}
if (newoffset == -1800) {
pics.style.left = 0 + 'px';
}
} else {
pics.style.left = offset + 'px';
}
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function(){buttonsShow(i)},false);
}
function buttonsShow(_index = index) {
console.log(_index);
for (let i = 0; i < buttons.length; i++) {
if (Array.from(buttons[i].classList).includes('on')) {
buttons[i].classList.remove('on');
}
}
buttons[_index].classList.add('on');
animate(_index*(-600), false);
3.
注意一下,next.onclick()这个用法。
function play() {
timer = setInterval(function(){
// animate(-600, true);
next.onclick();
}, 2000);
}
Log in
to use Ginger
Limited mode
;}