首页 > 代码库 > Javascript学习记录——原生JS实现旋转木马特效
Javascript学习记录——原生JS实现旋转木马特效
昨天学习到了JS特效部分,然后老师讲了旋转木马特效的实现,如上图。不过只是讲了通过点击箭头实现图片的切换,对于点击图片本身以及二者联动却是没有讲解。
本着一颗追求完美的心,今天花费了一个中午终于将整个功能全部完善(死了太多脑细胞~~)。
接下来直接进入主题哈~(主要讲解JS,所以对其中的HTML及CSS不做详细说明。)
首先是HTML代码
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>旋转木马轮播图</title> <link rel="stylesheet" href="http://www.mamicode.com/css/css.css"/> </head> <body> <div class="wrap" id="wrap"> <div class="slide" id="slide"> <ul> <li><a href="http://www.mamicode.com/#"><img src="http://www.mamicode.com/images/slidepic1.jpg" /></a></li> <li><a href="http://www.mamicode.com/#"><img src="http://www.mamicode.com/images/slidepic2.jpg" /></a></li> <li><a href="http://www.mamicode.com/#"><img src="http://www.mamicode.com/images/slidepic3.jpg" /></a></li> <li><a href="http://www.mamicode.com/#"><img src="http://www.mamicode.com/images/slidepic4.jpg" /></a></li> <li><a href="http://www.mamicode.com/#"><img src="http://www.mamicode.com/images/slidepic5.jpg" /></a></li> </ul> <div class="arrow" id="arrow"> <a href="javascript:;" class="prev" id="arrLeft"></a> <a href="javascript:;" class="next" id="arrRight"></a> </div> </div> </div> </body> </html>
以下为CSS代码:
blockquote, body, button, dd, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, hr, input, legend, li, ol, p, pre, td, textarea, th, ul { margin: 0; padding: 0 } body, button, input, select, textarea { font: 12px/1.5 "Microsoft YaHei", "微软雅黑", SimSun, "宋体", sans-serif; color: #666; } ol, ul { list-style: none } a { text-decoration: none } fieldset, img { border: 0; vertical-align: top; } a, input, button, select, textarea { outline: none; } /*以上为简单的初始化*/ a, button { cursor: pointer; } .wrap { width: 1200px; margin: 100px auto; } .slide { height: 500px; position: relative; } .slide li { position: absolute; left: 200px; top: 0; } .slide li img { width: 100%; } .arrow { opacity: 0; position: absolute; top: 200px; z-index: 1000; } .arrow .next{ left: 1120px; } .prev, .next { width: 76px; height: 112px; position: absolute; top: 50%; margin-top: -56px; background: url(../images/prev.png) no-repeat; z-index: 99; } .next { right: 0; background-image: url(../images/next.png);
以下为JS代码:先说明一下整体的思路
1、设置鼠标移入移出事件,显示图片中的左右两个箭头
2、设置左右两个箭头的点击事件,通过点击箭头,实现配置单中的样式切换(因为获取图片所在的li是一个伪数组,无法使用数组的方法,所以图片的轮播实现主要是通过对配置单内元素索引的改变完成的。)
3、设置点击li(也就是图片,后面统一写为li)标签实现点击的li出现在界面最前端。
4、实现点击箭头后,点击li的联动,也就是无论点击箭头多少次,再次点击li后,也能将当前点击的li显示在界面最前端。
5、实现点击li后,点击箭头的联动,也就是无论点击li后当前界面最前端的li是哪一个,再次点击箭头后,都能按照点击箭头的规则切换到当前li的下一个或上一个li。
是不是有点啰嗦啊 0...0
<script>
(function(){//今天偶尔看到一篇问文章,其中说道用自调用函数封装自己的JS代码可以防止环境污染哈~好像还有其他很好的功能,还没有详细了解,这里就拿出来爽一下~ var config = [ { width: 400, top: 20, left: 50, opacity: 0.2, zIndex: 2 },//0 { width: 600, top: 70, left: 0, opacity: 0.8, zIndex: 3 },//1 { width: 800, top: 100, left: 200, opacity: 1, zIndex: 4 },//2 { width: 600, top: 70, left: 600, opacity: 0.8, zIndex: 3 },//3 { width: 400, top: 20, left: 750, opacity: 0.2, zIndex: 2 }//4 ];//config为一个配置单 规定了每张图片的大小位置层级透明度 //获取元素 var wrap = document.getElementById(‘wrap‘) var slide = document.getElementById(‘slide‘) var ul = slide.children[0] var lis = ul.children var arrow = document.getElementById(‘arrow‘) var arrLeft = document.getElementById(‘arrLeft‘) var arrRight = document.getElementById(‘arrRight‘) //4.3 因为要控制图片显示的频率,因此要等到每次图片正常切换完成才进行下一次图片切换,假设初始时图片切换完成 flag == true var flag = true, count = 2, cFlag = false //7.92 这里设置cFlag初始值为false /** * 缓动改变样式的函数,先封装这个函数,后面进行配置单内的样式添加只需要调用这个函数就可以了。 * @param tag 要改变的元素 * @param json 一个对象,里面以属性保存要修改的样式 * @param fn 传入函数,可以在第一次调用执行后,再执行传入的函数体内容 */ function perfectAnimate(tag, json, fn) { clearInterval(tag.timer)//首先清除定时器 tag.timer = setInterval(function () {//新建一个setInterval,设置周期时间为18ms,这个数字不固定~ var flag = true //这里运用了假设成立法 for (var k in json) { //遍历传入的对象 if (k == ‘opacity‘) { //判断对象中保存的样式,如果为opacity,单独设置 var leader = getStyle(tag, k) * 100 //这里获取opacity的初始值。这里使用的是下面获取样式的兼容函数。
//乘100是因为JS小数计算存在精度问题。所以这里在下将其扩大100倍进行使用。 var target = json[k] * 100 //这里取出opacity的目标值,也就是对象中的样式属性值 var step = (target - leader) / 10; //这里用目标值减去初始值除以10达到缓动的目的(变速改变) step = step > 0 ? Math.ceil(step) : Math.floor(step);//这里也可以不使用这个判断,对step向上或向下保证最后的时候每次都能至少走1 leader = leader + step; //初始值加上每次增加或减少的值 tag.style[k] = leader / 100; //将值设置给opacity属性,当然别忘了把100除回来~ } else if (k == ‘zIndex‘) { tag.style[k] = json[k] //如果为zIndex 这时候就直接设置给样式就行了。 } else { var leader = parseInt(getStyle(tag, k)) || 0 //当样式属性值为带单位的时候,这时候要进行一个取整去掉单位,同时为了防止没有初始值而默认为auto,使用一个短路操作 var target = json[k] var step = (target - leader) / 10 step = step > 0 ? Math.ceil(step) : Math.floor(step) leader = leader + step tag.style[k] = leader + ‘px‘ } if (target != leader) { flag = false //这里只要有一个没有样式没有设置完成,假设便不成立,便不执行下面的清除 } } if (flag) { //假设所有属性都设置完成,便清除定时器,节约空间 clearInterval(tag.timer) // if ("function" == typeof fn) { // fn() // } fn && fn();//清除定时器后,可能会有需要的功能要在以上执行完成后执行,这里使用一个回调函数,
//同样使用短路操作,防止没有传函数而报错。上面注释的与这个功能一样,只是更严谨,这个更简洁 } }, 18) } /** * 获取多个样式的方法的兼容性。current是IE自己的,不支持getComputedStyle * @param tag 是标签名 * @param attr 是具体的某个样式的属性 * @returns {*} 返回的是这个样式的属性值 */ function getStyle(tag, attr) { if (tag.currentStyle) { return tag.currentStyle[attr]; } else { return getComputedStyle(tag, null)[attr]; } } //以上准备工作做完,然后可以开始我们代码代码的书写了。
//哎呀,好困呀~~还好后面的内容在我中午写的时候已经做好注释了,省了不少时间哇,哈哈哈哈....
//建议以序号顺序进行阅读哈,虽然自我感觉挺详细的,但是可能存在某些表述不准确的地方,望谅解~毕竟这才是我的第二篇博客啦...
/** * 1 封装函数,将config中的样式一一对应设置给li标签,因为后面会多次调用它 */ function change() { for (var i = 0; i < config.length; i++) { perfectAnimate(lis[i], config[i], function () { flag = true//4.6 这里在perfectAnimate函数中传入函数,赋值flag为true,因为这时候图片切换已经完成,可以进行下一次切换了。
}) } }
//2 最开始的默认分布(也就是上图那个效果~),直接调用函数
change() //3 设置移入移出事件,显示与隐藏箭头
wrap.onmouseover = function () { perfectAnimate(arrow, {‘opacity‘: 1}) } wrap.onmouseout = function () { perfectAnimate(arrow, {‘opacity‘: 0}) } /** * */ function clearChangeConfig2() { if (cFlag) { //7.93 因为最开始点击箭头时,没有进行li的点击切换,不需要进行联动,只有点击过li后,再点击箭头才执行,可以 // 使用cFlag作为判断条件 cFlag = false //7.91 这里被调用是需要执行changeConfig1(this.index)里面的if判断,所以首先设置cFlag为false for (var i = 0; i < lis.length; i++) { //7.4 因为点击li后图片进行切换后,config中的元素又回到初始位置,与设置给li的并不相同,这时就需要 // 将点击li时最后执行的changeConfig2(this.index),再退回到changeConfig1(this.index),要做到上一 // 步,就需要找到当前点击的li的索引,因为当前点击的li肯定是设置有属性zIndex=4,并且其index属性保存了 // 其索引值,所以通过这就可以找到需要的索引了。 if (lis[i].style.zIndex == 4) { changeConfig1(lis[i].index)//7.5 找到索引后直接将config中元素的位置退回到changeConfig1(this.index)
} } } } function pushCount() { if (count <= config.length && count > 0) { count-- } else { count = 4 } } function unshiftCount() { if (count < config.length && count >= 0) { count++ } else { count = 0 } } //4 添加右箭头点击事件
arrRight.onclick = function () { if (flag) {//4.4 如果flag为true,即4.3假设成立,便执行下面的代码 flag = false//4.5 给flag设置为false,使假设不成立,这时候只要没有改变flag值,点击事件源将不在执行这里的代码,不过此次下面代码依旧会执行 clearChangeConfig2()//7.3 这里调用是为了点击li后,实现箭头的联动,具体看7.4 config.push(config.shift())//4.1 点击后将第一个样式剪切到最后一个 change()//4.2 通过调用函数,将新生成的config元素一一对应设置给li标签
//7.0 点击箭头后,实现与li点击联动,每次点击右箭头count值都会自减一次,count初始值为2.小于0后直接赋值为4
//count的值对应的是每次进行切换后,zIndex值为4的元素在config中的索引值,并以此为初始值(取代6.5设置的2)——界面最前端的配置属性,使得点击li时正常切换
pushCount() } } //5 添加左箭头点击事件
arrLeft.onclick = function () { if (flag) { flag = false clearChangeConfig2() config.unshift(config.pop())//5.1 点击后将最后一个样式剪切到第一个 change() //5.2 通过调用函数,将新生成的config元素一一对应设置给li标签 unshiftCount()//7.1 将箭头点击与li点击联动,每次点击左箭头count值都会自增一次,count初始值为2.大于4后直接赋值为0 } } /** * 6.3 根据当前点击的li的索引值,给li设置config内元素中的属性 * @param i 当前点击li的索引值 */ function changeConfig1(i) { //6.5 未设置联动时,count位置应该是数字2,即最开始时zIndex值为4的config元素对应的索引,改变的中心思想为:zIndex中值为4的config元素(界面最前端的配置属性)
// 总是设置给当前的点击的li,保持二者索引一致(count先不管) //6.6 然后进行判断,如果当前li的索引值小于等于2 //7.2 这时将count作为条件依据,等同于以zIndex值为4的元素的索引为依据 if (i <= count) { for (var i = i; i < count; i++) { config.push(config.shift())//6.7 那么根据其索引值进行0位config元素的剪切,直到zIndex值为4的config元素对应的索引与当前点击的li的索引一致 if (cFlag == false) {//7.8 又因为点击li时,config元素与li进行对应设置后,又会回到初始位置,不会影响到count的变化 //所以点击li时调用这个函数不能执行这个if判断,这里再次使用假设成立法,假设cFlag为false时执行if判断成立 pushCount()//7.6 因为将config中元素的位置改为正常的后,zIndex=4的元素的索引又会发生变化,所以这时count也要跟着变化 i--//7.7 这里的count为--,所以i值也要--才能保证count自减的次数与循环执行次数一致(即与config元素剪切的次数一致,即保证count的值总是配置单中zIndex=4的元素的索引) } } } else { for (var i = i; i > count; i--) { config.unshift(config.pop())//6.8 如果大于2,那么根据其索引值进行索引为4的config元素的剪切,直到zIndex值为4的config元素对应的索引与当前点击的li的索引一致 if (cFlag == false) { unshiftCount() i++ } } } } function changeConfig2(i) { if (i <= count) { for (var i = i; i < count; i++) { config.unshift(config.pop()) if (cFlag == false) { pushCount() } } } else { for (var i = i; i > count; i--) { config.push(config.shift()) if (cFlag == false) { unshiftCount() } } } } //6 遍历lis,给li设置点击事件
for (var i = 0; i < lis.length; i++) { lis[i].index = i//6.4 以lis[i]的自定义属性index保存其索引值 lis[i].onclick = function () {//6.1 添加事件 cFlag = true //7.9 点击li时执行changeConfig1(this.index)之前将cFlag赋值true,即不执行它里面的if判断 changeConfig1(this.index)//6.2 点击li后将zIndex值为4的config元素添加给当前点击的li,其他元素依次挪动。 change()//6.9通过调用函数,将修改后的config内的元素属性一一对应设置给li changeConfig2(this.index)//6.91 在第6.2步中,zIndex值为4config元素进行过剪切可能已经不在索引为2的位置了,这时后我们再进行与6.2步中相反的操作 // 将config中的元素位置回归到初始状态,但是并不设置给li. } }
}())
</script>
最后本着负责的态度,还是认真对着上面的文字检查了20分钟,确保在自己水平内将问题讲清楚——可是、好像还是不够太具体化...
看来只能等以后的理解更加透彻后,再对本文进行修改了。
——BY:骑士与魔王(我微博也是这个名字哈,里面还有自己之前玩微博时写的图文小说,《骑士与魔王》,可惜太监啦,哈哈哈哈)
Javascript学习记录——原生JS实现旋转木马特效