告别组件之教你使用原生js和css写移动端轮播图
在工作中由于项目需要要写一个轮播图,本想使用组件直接调用实现快速开发,但是一想到自己经常使用组件但是让自己手写的话确实一点都不会。
一个不会手写组件的前端程序员不是一个好程序员!于是打算自己手写一个。
老规矩,首先看一下最终效果,这个最终可以实现定时自动播放,触摸滑动,手动修改下面横条效果等功能。
项目中使用到的HTML代码如下
<div class="banner">
<ul class="clearfix">
<li><a href="#"><img
src="./assets/images/loginPage/banner2.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner1.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner2.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner1.png"></a></li>
</ul>
<ul>
<li class="now"></li>
<li></li>
</ul>
</div>
原理简介
在这里我们以两张轮播图为例,由图上可以注意到,我们一共放了四张轮播图,为什么呢?因为我们在向右滑动的过程中为了保证某张轮播图的右边始终有一个轮播图可以看到,于是我们把轮播图的最后一张放在最前面,轮播图的第一张放在最后面。当轮播图滑到最后面的时候,此时消除动画效果,把轮播图的transformX调到第二张轮播图的所在位置,如上图所示。
自动播放
首先我们来实现以下自动播放,项目中我没有使用jQuery,而是直接使用的原生js,我们先来获取一下dom元素。
要注意:在移动端并没有使用px
作为定位距离单位,而是使用的相对距离,在这里我是基于750px的设计稿进行计算,按照我的计算规则:
也就是说,设计稿上的100px相当于我的html界面的1rem,具体的rem应用方法请移步我的👉另一篇博客👈
var imageCount = 2; //页面中用来轮播的图片有2张不同的
//轮播图大盒子
var banner = document.querySelector(".banner");
//图片的宽度,相当于width:100%
var width = 7.5;
//图片盒子
var imageBox = banner.querySelector("ul:first-child");
//点盒子
var pointBox = banner.querySelector("ul:last-child");
//所有的点
var points = pointBox.querySelectorAll("li");
在之后我们还会用到监听组件过渡动画结束之后触发的函数,因为我们要兼容webkit内核的浏览器,所以要写两个,这个我们不妨对此函数做一下封装,在一个函数里面进行两种浏览器的操作。
/*定义的一个命名空间*/
window.my = {};
/*封装一个事件 过渡结束事件*/
my.transitionEnd = function(dom,callback){
//1.给谁加事件
//2.事件触发后处理什么业务
if(!dom || typeof dom != 'object'){
//没dom的时候或者不是一个对象的时候 程序停止
return false;
}
// 非webkit内核
dom.addEventListener('transitionEnd', function(){
callback && callback();
});
// webkit内核
dom.addEventListener('webkitTransitionEnd', function(){
callback && callback();
});
}
接下来我们实现一下添加过渡效果,消除过渡效果,给轮播图定位,设置定时器,轮播图过渡效果结束之后触发的函数。
//加过渡
var addTransition = function() {
imageBox.style.transition = "all 0.3s";
imageBox.style.webkitTransition = "all 0.3s"; /*做兼容*/
};
//清除过渡
var removeTransition = function() {
imageBox.style.transition = "none";
imageBox.style.webkitTransition = "none";
};
//定位
var setTranslateX = function(translateX) {
imageBox.style.transform = "translateX(" + translateX + "rem)";
imageBox.style.webkitTransform = "translateX(" + translateX + "rem)";
};
//功能实现
//自动轮播 定时器 无缝衔接 动画结束瞬间定位
var index = 1;
var timer = setInterval(function() {
index++; //自动轮播到下一张
//改变定位 动画的形式去改变 transition transform translate
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
}, 3000);
//等过渡结束之后来做无缝衔接
my.transitionEnd(imageBox, function() {
/*
* 这边就出现了我们上面示意图中的transformX由最后一张图片的位置移动到第二张图片的位置
* 1. 取消过渡动画
* 2. 定位
*/
if (index > imageCount) {
index = 1;
} else if (index <= 0) {
index = imageCount;
}
removeTransition(); //清除过渡
setTranslateX(-index * width); //定位
setPoint(); //设置底部显示当前图片对应的圆角
});
// 这个函数的效果就是改变轮播图小横条的样式,把在当前位置的小横条填充成白色,具体样式可以自定义
var setPoint = function() {
//清除上一次的now
for (var i = 0; i < points.length; i++) {
points[i].className = " ";
}
//给图片对应的点加上样式
points[index - 1].className = "now";
};
到这里我们就可以实现轮播图定时滚动了。
触摸滚动
手指滑动的时候调用touch事件让轮播图滑动,记录坐标轴的改变 改变轮播图的定位(位移css3),当滑动的距离不超过一定的距离的时候 需要吸附回去 过渡的形式去做, 当滑动超过了一定的距离 需要 跳到 下一张或者上一张 (滑动的方向) 一定的距离(屏幕的三分之一)
//touch事件
var startX = 0; //记录起始 刚刚触摸的点的位置 x的坐标
var moveX = 0; //滑动的时候x的位置
var distanceX = 0; //滑动的距离
var isMove = false; //是否滑动过
imageBox.addEventListener("touchstart", function(e) {
clearInterval(timer); //清除定时器
startX = e.touches[0].clientX; //记录起始X
});
imageBox.addEventListener("touchmove", function(e) {
moveX = e.touches[0].clientX; //滑动时候的X
distanceX = moveX - startX; //计算移动的距离
//计算当前定位 -index*width+distanceX
removeTransition(); //清除过渡
setTranslateX(-index * width + distanceX / 100); //实时的定位
isMove = true; //证明滑动过
});
//在模拟器上模拟的滑动会有问题 丢失的情况 最后在模拟器的时候用window
imageBox.addEventListener("touchend", function(e) {
// 滑动超过 1/3 即为滑动有效,否则即为无效,则吸附回去
if (isMove && Math.abs(distanceX) > width / 3) {
//5.当滑动超过了一定的距离 需要 跳到 下一张或者上一张 (滑动的方向)*/
if (distanceX > 0) {
//上一张
index--;
} else {
//下一张
index++;
}
}
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
if (index > imageCount) {
index = 1;
} else if (index <= 0) {
index = imageCount;
}
setPoint();
//重置参数
startX = 0;
moveX = 0;
distanceX = 0;
isMove = false;
//加定时器
clearInterval(timer); //严谨 再清除一次定时器
timer = setInterval(function() {
index++; //自动轮播到下一张
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
}, 3000);
});
由以上代码可以实现一个轮播图效果。
全部代码
HTML
<div class="banner">
<ul class="clearfix">
<li><a href="#"><img
src="./assets/images/loginPage/banner2.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner1.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner2.png"></a></li>
<li><a href="#"><img
src="./assets/images/loginPage/banner1.png"></a></li>
</ul>
<ul>
<li class="now"></li>
<li></li>
</ul>
</div>
JS
/*
* 1.自动轮播 定时器 无缝衔接 动画结束瞬间定位
* 2.点需要随着轮播的滚动改变对应的点 改变当前样式 当前图片的索引
* 3.手指滑动的时候让轮播图滑动 touch事件 记录坐标轴的改变 改变轮播图的定位(位移css3)
* 4.当滑动的距离不超过一定的距离的时候 需要吸附回去 过渡的形式去做
* 5.当滑动超过了一定的距离 需要 跳到 下一张或者上一张 (滑动的方向) 一定的距离(屏幕的三分之一)
* */
/*定义的一个命名空间*/
window.my = {};
/*封装一个事件 过渡结束事件*/
my.transitionEnd = function(dom,callback){
//1.给谁加事件
//2.事件触发后处理什么业务
if(!dom || typeof dom != 'object'){
//没dom的时候或者不是一个对象的时候 程序停止
return false;
}
// 非webkit内核
dom.addEventListener('transitionEnd', function(){
callback && callback();
});
// webkit内核
dom.addEventListener('webkitTransitionEnd', function(){
callback && callback();
});
}
var imageCount = 2; //页面中用来轮播的图片有2张不同的
//轮播图大盒子
var banner = document.querySelector(".banner");
//图片的宽度
var width = 7.5;
//图片盒子
var imageBox = banner.querySelector("ul:first-child");
//点盒子
var pointBox = banner.querySelector("ul:last-child");
//所有的点
var points = pointBox.querySelectorAll("li");
//公用方法
//加过渡
var addTransition = function() {
imageBox.style.transition = "all 0.3s";
imageBox.style.webkitTransition = "all 0.3s"; /*做兼容*/
};
//清除过渡
var removeTransition = function() {
imageBox.style.transition = "none";
imageBox.style.webkitTransition = "none";
};
//定位
var setTranslateX = function(translateX) {
imageBox.style.transform = "translateX(" + translateX + "rem)";
imageBox.style.webkitTransform = "translateX(" + translateX + "rem)";
};
//功能实现
//自动轮播 定时器 无缝衔接 动画结束瞬间定位
var index = 1;
var timer = setInterval(function() {
index++; //自动轮播到下一张
//改变定位 动画的形式去改变 transition transform translate
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
}, 3000);
//等过渡结束之后来做无缝衔接
my.transitionEnd(imageBox, function() {
//处理事件结束后的业务逻辑
if (index > imageCount) {
index = 1;
} else if (index <= 0) {
index = imageCount;
}
removeTransition(); //清除过渡
setTranslateX(-index * width); //定位
setPoint(); //设置底部显示当前图片对应的圆角
});
//改变当前样式 当前图片的索引
var setPoint = function() {
//清除上一次的now
for (var i = 0; i < points.length; i++) {
points[i].className = " ";
}
//给图片对应的点加上样式
points[index - 1].className = "now";
};
/*
手指滑动的时候让轮播图滑动 touch事件 记录坐标轴的改变 改变轮播图的定位(位移css3)
当滑动的距离不超过一定的距离的时候 需要吸附回去 过渡的形式去做
当滑动超过了一定的距离 需要 跳到 下一张或者上一张 (滑动的方向) 一定的距离(屏幕的三分之一)
*/
//touch事件
var startX = 0; //记录起始 刚刚触摸的点的位置 x的坐标
var moveX = 0; //滑动的时候x的位置
var distanceX = 0; //滑动的距离
var isMove = false; //是否滑动过
imageBox.addEventListener("touchstart", function(e) {
clearInterval(timer); //清除定时器
startX = e.touches[0].clientX; //记录起始X
});
imageBox.addEventListener("touchmove", function(e) {
moveX = e.touches[0].clientX; //滑动时候的X
distanceX = moveX - startX; //计算移动的距离
//计算当前定位 -index*width+distanceX
removeTransition(); //清除过渡
setTranslateX(-index * width + distanceX / 100); //实时的定位
isMove = true; //证明滑动过
});
//在模拟器上模拟的滑动会有问题 丢失的情况 最后在模拟器的时候用window
imageBox.addEventListener("touchend", function(e) {
// 滑动超过 1/3 即为滑动有效,否则即为无效,则吸附回去
if (isMove && Math.abs(distanceX) > width / 3) {
//5.当滑动超过了一定的距离 需要 跳到 下一张或者上一张 (滑动的方向)*/
if (distanceX > 0) {
//上一张
index--;
} else {
//下一张
index++;
}
}
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
if (index > imageCount) {
index = 1;
} else if (index <= 0) {
index = imageCount;
}
setPoint();
//重置参数
startX = 0;
moveX = 0;
distanceX = 0;
isMove = false;
//加定时器
clearInterval(timer); //严谨 再清除一次定时器
timer = setInterval(function() {
index++; //自动轮播到下一张
addTransition(); //加过渡动画
setTranslateX(-index * width); //定位
}, 3000);
});
CSS
*,
::before,
::after{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;/*兼容移动端主流浏览器*/
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;/*清除移动端点击高亮效果*/
}
body{
font-family:Microsoft YaHei,sans-serif;/*移动端默认的字体*/
font-size: 14px;
color: #333;
}
ol,ul{
list-style: none;
}
/*清除浮动*/
.clearfix::before,
.clearfix::after{
content: "";
display: block;
height: 0;
line-height: 0;
visibility: hidden;
clear: both;
}
.banner{
width: 7.5rem;
overflow: hidden;
position: relative;
}
.banner ul:first-child{
width: 1000%;
-webkit-transform: translateX(-10%);
transform: translateX(-10%);
}
.banner ul:first-child li{
width: 10%;
float: left;
}
.banner ul:first-child li a{
display: block;
width: 7.5rem;
}
.banner ul:first-child li a img{
width: 7.5rem;
display: block;
}
.banner ul:last-child{
position: absolute;
bottom: 6px;
width: 100%;
text-align: center;
}
.banner ul:last-child li{
width: 6px;
height: 6px;
border: 1px solid #fff;
border-radius: 0.02rem;
width: 0.24rem;
height: 0.04rem;
display: inline-block;
margin-left: 10px;
}
.banner ul:last-child li:first-child{
margin-left: 0;
}
.banner ul:last-child li.now{
background: #fff;
}