用原生JS实现3D轮播效果
实现思路:
- 先实现无缝轮播效果
- 添加3D效果
- 完善代码
- 增加自动轮播效果
效果如下:
视图中显示3张图片,并通过CSS的透视和旋转实现3D效果,当无任何操作时,图片自动循环轮播。当鼠标划到轮播图时,则停止轮播,滑出时继续自动轮播,此效果通过触发mouseenter和mouseleave事件和计时器来实现。当鼠标在轮播图上向左右滑动时,根据方向轮播下一站或上一张图片,通过触发mousedown和mouseup事件时,的鼠标的clientX来判断方向。
无缝轮播思路
视图中都会显示3张图片,当我们轮播到最后一张图片时,我们该怎么办呢???
我的思路:
如果要达到循环轮播效果,那么最后一张图片结束后,我们就应该继续轮播第一张图片,当最后一张图片为视图中的最左侧的图片时则视图中显示的图片应当是最后一张、第一张和第二张图片,为了能让这三张图片组合到一起,所以我在最后添加了第一张图片和第二张图片,当继续轮播时,则从正方向的第一张和第二张图片开始(相当于我们开到的是倒数的两张,但轮播时是正数的两张开始),如果是逆向轮播,则看到的是正序的前两张,轮播时从倒序轮播。
思路图大致如下:
用一句话就是:正序的0、1与逆序的0、1进行偷梁换柱,达到循环轮播效果。
代码
HTML部分:
<div class="container">
<div class="wrapper">
<div class="slide pre-slide">
<img src="img/img1.jpg">
</div>
<div class="slide cur-slide">
<img src="img/img2.jpg">
</div>
<div class="slide next-slide">
<img src="img/img3.jpg">
</div>
<div class="slide">
<img src="img/img4.jpg">
</div>
</div>
</div>
CSS部分
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: #000;
}
.container {
margin: 50px auto;
width: 1000px;
height: 500px;
/*background-color: #ececec;*/
overflow: hidden;
position: relative;
}
.wrapper {
height: 500px;
width: 900px;
/*border: 1px solid #000;*/
/*注意:这里解决了slide的子元素之间的间距问题————即使设置了margin和padding时,子元素之间仍有间距(子元素之间存在空格或换行字符),所以这里需要设置字体大小为0*/
font-size: 0px;
/**/
position: absolute;
left: 0;
top: 0;
}
.wrapper:hover {
cursor: pointer;
}
.slide {
width: 300px;
height: 500px;
display: inline-block;
border: none;
font-size: 32px;
font-weight: 900;
text-align: center;
perspective: 600px;
}
.slide img {
width: 100%;
height: 100%;
transform: translateZ(-500px);
}
.pre-slide img {
/*background-color: rgb(56, 233, 50);*/
transform: translateZ(0px) rotateY(45deg);
transition: all 1.5s;
}
/**/
.cur-slide img {
/*background-color: rgb(57, 180, 236);*/
transform: translateZ(100px);
transition: all 1.5s;
}
.next-slide img {
/*background-color: #f4f81c;*/
transform: translateZ(0px) rotateY(-45deg);
transition: all 1.5s;
}
JS部分
<script>
/*
*
* distance用来记录每次轮播时的偏移量
* prePos用来记录轮播图最左边元素的Index
* downPos用来记录mousedown时鼠标的clientX
* flag用来记录窗口移动的方向,true向左,false向右
*
*
*
*/
let wrapper, slides, container, downPos, prePos, timeID, num;
init();
function init() {
prePos = 0;
flag = true;
wrapper = document.querySelector('.wrapper')
slides = document.getElementsByClassName('slide')
container = document.querySelector('.container')
num = slides.length;
let firstNode = slides[0].cloneNode(true);
let secondNode = slides[1].cloneNode(true);
wrapper.appendChild(firstNode);
wrapper.appendChild(secondNode);
distance = slides[0].clientWidth;
//初始化处理
//根据slide的多少动态设置wrapper的宽度
wrapper.style['width'] = slides.length * distance + 'px';
container.style.width = 3 * distance + 'px';
//wrapper.style.left = -distance + 'px';
//console.log(wrapper.style);
timeID = setInterval(move, 3000);
wrapper.addEventListener('mousedown', handelMouseDown, true);
wrapper.addEventListener('mouseenter', handleMouseenter);
wrapper.addEventListener('mouseleave', handleMouseleave);
}
function move() {
updateClassName(prePos, 'slide')
updateClassName(prePos + 1, 'slide')
updateClassName(prePos + 2, 'slide')
if (flag) {
if (prePos === slides.length - 3) {
prePos = 0;
} else {
prePos++;
}
} else {
if (prePos === 0) {
prePos = slides.length - 3;
} else {
prePos--;
}
}
//console.log(prePos);
wrapper.style.left = -prePos * distance + 'px';
console.log(prePos);
updateClassName(prePos, 'slide pre-slide')
updateClassName(prePos + 1, 'slide cur-slide')
updateClassName(prePos + 2, 'slide next-slide')
}
function updateClassName(pos, name) {
switch (pos) {
case 0:
case slides.length - 2:
console.log(name);
slides[0].className = slides[slides.length - 2].className = name
break;
case 1:
case slides.length - 1:
slides[1].className = slides[slides.length - 1].className = name
break;
default:
slides[pos].className = name;
break;
}
}
function handelMouseUp(e) {
//console.log(e.clientX);
if (e.clientX < downPos) {
//console.log('左');
flag = true;
} else if (e.clientX > downPos) {
//console.log('右');
flag = false;
}
move();
}
function handleMouseenter(e) {
//console.log('enter');
clearInterval(timeID);
}
function handleMouseleave() {
//console.log('leave');
flag = true;
timeID = setInterval(move, 3000)
}
function handelMouseDown(e) {
//console.log(e.target);
//此处解决连续对同一个sliede的mousedown和mouseup使用一次后失效的问题
//原因:
//触发了浏览器的 drag 操作,导致mouseup丢失。
//由于鼠标离开了操作的区域,触发了mouseleave导致mouseup丢失。
e.preventDefault(); //我可能遇到的是第一种情况
//e.stopPropagation();
downPos = e.clientX;
wrapper.addEventListener('mouseup', handelMouseUp);
}
</script>
在实现过程中可能会遇到一些问题,我遇到的都体现在代码注释里面啦。
总结一下:
在开始的时候我为了简单化问题,于是我是只用了几个div进行轮播,当基本上实现无缝轮播的时候,我在另一个文件中进行图片的3D效果测试(我也刚接触前端不久,对CSS3的动画掌握也不太熟练),当觉得两个效果都差不多时再合并代码,将图片的3D效果添加到轮播图中。合并后,也出现了一个问题,比如当轮播到最后一张后继续正向轮播和到第一张后要逆向轮播时,偷梁换柱的过程中,会出现轮播效果和前一次相同方向轮播的效果不一致的问题。关于这一点,我被自己绕了很久,当后面想通后,也就清晰明了了。出现这个问题的原因是因为我在代码实现的过程中,将正序的0、1和逆序的0、1独立化了,而它们本不应该相互独立,如果独立了,它们就存在className不一致的问题,那么它们在轮播前的一次状态就不一样,那么效果的呈现也就不一样了,于是出现了updateClassName方法,当修改每个slide的className时,不管是正序0\1或是倒叙的,其正序的0与倒叙的0、正序的1和倒叙的1的className都要更新为最新的className,才能达到做好偷梁换柱。
除此之外,也遇到没有记住的小问题:
- 为什么将margin和padding设置为0后,容器之间还有间隙?
容器之间存在空格字符或换行符,都视为一个空格字符,这个字符也有大小,也就是我们所见的间隙,将其父容器的font-size设为0则可以解决。 - mouseup触发一次后失效的问题:
当mousedown触发后,可能会触发浏览器的默认事件,在mousedown的回调函数中使用e.preventDefault()禁止触发默认事件就可以。
最后
我把代码放到仓库啦,代码还有待完善的空间,请各位多多指教:
链接: 代码.