(本文章不做过多的解释,基本上每行JS代码都有对应的注释。)
下面代码,复制粘贴就可以拿过去自己尝试一下啦!不过照片需要你自己更换本地图片。
其中有我个人封装的简单运动animate,其中的animate()函数即是,最下面有该运动的源码,需要一起复制到另一个js文件 并用script标签引入来使用,这封装好的简单运动也建议大家看看噢!
本轮播图的主要操作功能有:
①点击左右按钮更换上下页
②拖拽滚动(必须先阻止浏览器的默认行为才能实现拖拽)
③无限轮播
④自动轮播
⑤点击指示器更换页数
⑥鼠标进入区域停止自动轮播、鼠标移出则回复自动轮播
效果图:
以下就是全部代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>轮播图</title>
<script src="animate.js"></script>
<style>
* {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
}
li {
list-style: none;
}
img {
display: block;
}
.box {
width: 600px;
height: 400px;
border: 1px solid black;
margin: 50px auto;
overflow: hidden;
position: relative;
}
.imgBox {
width: 500%;
height: 100%;
display: flex;
position: absolute;
left: 0px;
}
.imgBox li {
width: 600px;
height: 100%;
}
.imgBox li img {
width: 600px;
height: 100%;
}
.lrTabs .prev,.next {
width: 20px;
height: 40px;
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgb(61, 61, 61);
text-align: center;
font: 800 20px/40px "宋体";
color: #fff;
cursor: pointer;
z-index: 10;
opacity: .5;
}
.next {
right: 0;
}
.pointBox {
display: flex;
position: absolute;
justify-content: space-between;
/* 没有动态变化的小圆点的样式 */
/* width: 250px; */
/* left: calc(50% - 115px); */
left: calc(50% - 60px);
bottom: 20px;
}
.pointBox li {
/* 没有动态变化的小圆点的样式 */
/* width: 40px;
height: 5px; */
/* border-radius: 12%; */
width: 30px;
height: 4px;
border-radius: 10%;
background-color: #ccc;
cursor: pointer;
margin-right: 10px;
transition: all .9s;
}
.pointBox .active {
background-color: aquamarine;
}
</style>
</head>
<body>
<div class="box">
<ul class="imgBox">
<li class="slider"><img src="./img/02.jpg"></li>
<li class="slider"><img src="./img/03.jpg"></li>
<li class="slider"><img src="./img/07.jpg"></li>
<li class="slider"><img src="./img/09.jpg"></li>
<li class="slider"><img src="./img/10.jpg"></li>
</ul>
<ol class="pointBox"></ol>
<div class="lrTabs">
<a class="prev"><</a>
<a class="next">></a>
</div>
</div>
<script>
var box = document.querySelector(".box")
var ul = document.querySelector(".imgBox")
var prev = document.getElementsByClassName("prev")
var next = document.getElementsByClassName("next")
var lis = document.querySelectorAll(".slider")
var lisWidth = parseFloat(getComputedStyle(lis[0]).width)
var lrTabs = document.querySelector(".lrTabs")
var ol = document.querySelector(".pointBox")
var imgs = document.querySelectorAll("img")
var currentIndex = 1
// 封装切换上一张图的函数
function prev1() {
--currentIndex
animate(
ul, {
left: -lisWidth * currentIndex + "px"
},
500,
function () {
// console.log("动画结束");
// console.log(currentIndex);
onAnimationEnd()
}
)
}
// 封装切换下一张图的函数
function next1() {
++currentIndex
animate(
ul, {
left: -lisWidth * currentIndex + "px"
},
500,
function () {
// console.log("动画结束");
// console.log(currentIndex);
onAnimationEnd()
}
)
}
// 通过事件委托来实现切换上下图的功能
lrTabs.addEventListener(
"click",
function (e) {
if (e.target.className === "next") {
next1()
} else if (e.target.className === "prev") {
prev1()
}
}
)
// 实现指示器随着图片的切换而高亮
var pointers = null
function lightHeightPointers() {
pointers.forEach(
function (p, index) {
if (index === currentIndex - 1) {
p.classList.add("active")
p.style.width = "60px"
p.style.height = "4px"
} else {
p.classList.remove("active")
p.style.width = "30px"
}
}
)
}
// 初始化指示器,即添加指示器
function initPointers() {
var fragment = document.createDocumentFragment()
lis.forEach(
function (l, index) {
var li = document.createElement("li")
fragment.appendChild(li)
}
)
ol.appendChild(fragment)
pointers = document.querySelectorAll("ol>li")
lightHeightPointers()
}
initPointers()
// 初始化无限轮播
function initLoop() {
var head = lis[lis.length - 1].cloneNode(true) // 第五张
var tail = lis[0].cloneNode(true)
ul.appendChild(tail)
ul.insertBefore(head, lis[0])
// 获取ul里的最新全部的li
var sliders = document.querySelectorAll("ul>li")
// 根据添加了最新的li来重新定义ul的宽度
ul.style.width = lisWidth * sliders.length + "px"
ul.style.left = -lisWidth + "px"
}
initLoop()
// 动画结束回调,真5跳假5,假1跳真1
function onAnimationEnd() {
currentIndex >= 6 && (currentIndex = 1)
currentIndex <= 0 && (currentIndex = 5)
ul.style.left = -lisWidth * currentIndex + "px"
lightHeightPointers()
}
// 实现 自动轮播
var timer = null
// 开始自动轮播
function startAutoPlay() {
if (!timer) {
timer = setInterval(function () {
next1()
}, 3000)
}
}
startAutoPlay()
// 停止自动轮播
function stopAutoPlay() {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 实现鼠标移入停止、鼠标移出后继续自动轮播的功能
box.onmouseenter = function (e) {
stopAutoPlay()
}
box.onmouseleave = function (e) {
startAutoPlay()
}
// 实现点击指示器切换幻灯片功能
function clickPointers() {
pointers.forEach(
function (p, index) {
p.onclick = function () {
currentIndex = index + 1
ul.style.left = -lisWidth * currentIndex + "px"
lightHeightPointers()
}
}
)
}
clickPointers()
// 阻止浏览器默认行为
imgs.forEach(
function (img, index) {
img.addEventListener(
"mousedown",
function (e) {
e.preventDefault()
}
)
}
)
/* 拖拽 */
// 标记是否正在拖拽
var isDragging = false
// 记录鼠标按下时的x位置
var downX = null
// 记录鼠标按下时ul的left值
var downUlLeft = null
/* 在ul身上按下鼠标,此处的对象要改成最大的div盒子即包含有左右tab的box,因为如果对象为ul时点击左右tab并且tab不属于ul的里面,所以系统会认为鼠标没有按下去但是鼠标松手的对象又是页面,所以会触发鼠标抬起的函数并导致downX的值为null */
box.onmousedown = function (e) {
// 标记开始拖拽
isDragging = true
// 同时记录鼠标按下的位置 + ul的初始left
downX = e.pageX
// console.log("downx","=",e.pageX);
downUlLeft = parseFloat(ul.style.left)
// console.log("此处是在onmousedown上按下鼠标");
}
/* 鼠标在任意位置撒手 */
document.onmouseup = function (e) {
// 不再拖拽ul
isDragging = false
// 获取鼠标的总偏移量
var offsetX = e.pageX - downX
// console.log("此处是在onmouseup上松开的鼠标");
// console.log(e.pageX);
// console.log(downX);
// console.log(offsetX);
/* 分情况讨论:上一下 OR 下一张 OR 还原拖拽前位置 */
switch (true) {
// 往右狂扫
case offsetX >= lisWidth / 3:
prev1()
break;
// 往左狂扫
case offsetX <= -lisWidth / 3:
next1()
break;
// case offsetX = 0:
// break;
// 有气无力地扫了一点点:还原扫前位置
default:
ul.style.left = -lisWidth * currentIndex + "px"
break;
}
}
/* 拖拽ul */
ul.onmousemove = function (e) {
// 如果鼠标已经down下
if (isDragging) {
// 鼠标的当前位置距离down下时的偏移量
var offsetX = e.pageX - downX
// 鼠标偏移量即ul的left偏移量
ul.style.left = downUlLeft + offsetX + "px"
// console.log("此处是在ul上拖拽鼠标");
}
}
</script>
</body>
</html>
下面是封装好的简单运动源码(注释已是相当详细了):
/* 从样式属性值中截取单位 */
// getUnit("200.123px")
function getUnit(value) {
// 定义所有的数字
var digits = "0123456789.-"
// 将入参先转换为string(以便计算字符数)
value = value + ""//200px
/* 遍历入参的每一个字符 */
for (var i = 0; i < value.length; i++) {
// 提取出每一个字符
var char = value[i]
// 看看当前字符是否是【数字字符】
// 如果不是【数字字符】 则当前位i一直到末尾皆为单位
if (digits.includes(char) === false) {
// 从当前位i一直截取到末尾 得到单位 返回之
return value.slice(i)
}
}
return ""
}
/**
* 多属性动画
* @param {Element} element 要做动画的元素
* @param {Object} targetObj 属性目标值的对象 封装了所有要做动画的属性及其目标值
* @param {number} timeCost 动画耗时,单位毫秒
* @param {Function} callback 动画结束的回调函数
* animate(box,{left:"200px",top:"500px",opacity:0.5},3000,()=>console.log("动画结束"))
*/
function animate(element, targetObj, timeCost = 1000, callback = null) {
// 定义每帧时长为40毫秒
var FRAEME_COST = 40
// 计算总帧数(半帧按一帧算)3000/40
var frames = Math.ceil(timeCost / FRAEME_COST)
// 记录当前运行到了第几帧
var currentFrame = 0
// 提前拿到动画前所有CSS属性 {left:0,top:0,opacity:1...}
var exStyles = window.getComputedStyle(element)
/* 每个CSS属性每帧应该偏移多少 */
// speed者 每帧偏移量也
var speedObj = {}
/* 计算每帧应该移动多少 */
// key分别为 left top opacity
for (var key in targetObj) {
// 拿出left属性的目标值 200px
var targetValue = targetObj[key]
speedObj[key] = {
// left每帧的偏移量
offset: (parseFloat(targetValue) - parseFloat(exStyles[key])) / frames,
// 将单位也揪出来
unit: getUnit(targetValue)
}
}
/* 开始做动画 */
var timer = setInterval(
/* 每40毫秒执行一下这个破函数:在上一帧(frame)的基础上 所有目标样式都按动画速度speedObj[key]偏移一丢丢 */
function () {
// 在每帧中对每个属性按动画速度做偏移
for (var key in speedObj) {
/* 拿出left属性每帧的偏移量 */
var offset = speedObj[key].offset
// 拿出left属性的单位
var unit = speedObj[key].unit
// 将元素的left设置为一个新的值(偏移了一丢丢)
// element.style.left = (0+2)+"px"
element.style[key] = parseFloat(getComputedStyle(element)[key]) + offset + unit
}
/* 看看够帧数了没有 */
if (++currentFrame === frames) {
// 帧数一满 停止动画
clearInterval(timer)
// 暴力矫正误差(将所有属性都打到目标值)
// {left:"200px",top:"500px",opacity:0.5}
for (var key in targetObj) {
// box.style.left = "200px"
element.style[key] = targetObj[key]
}
// 回调callback
callback && callback()
}
},
// 每40毫秒一帧
FRAEME_COST
);
}