^ _ ^ 最近参加了一个免费的华为在线课程,有一个作业是用原生js实现轮播图。练手的同时发现工作中框架用的多。原生JS不如以前那么熟练了。特此写这篇文章记录一下。希望能帮助到一些新手盆友。来一起学习吧。
实现以下4个需求:
自动无缝切换图片
鼠标左右拖动切换图片
点击小点切换对应图片
浏览器窗口尺寸改变不影响以上功能
*{
box-sizing: border-box;
}
body, ul{
margin: 0;
padding: 0;
font-family: PingFang SC,PingFangSC-Regular,Helvetica Neue,Microsoft YaHei Regular,Microsoft YaHei,宋体,"sans-serif";
font-size: 16px;
}
<div class=” page”>
.page{
overflow: hidden; //让视线范围内只显示一张图片
position: relative; //用于点的定位
}
<div class=”box”>
.box{
display: flex; //设置flex布局,默认从左到右依次排列。
}
不清楚flex的盆友可以看一下阮大神的文章:Flex 布局教程:语法篇
<div class="slideWrapper"> <a href="xxx" target="_blank">a>div>
.slideWrapper{
width: 100%;
height: 100%;
flex-shrink: 0;
}
需要注意的是除了需要设置宽高100%以外,还有设置flex-shrink: 0; 不让容器等比例缩小
.dots{
position: absolute;
bottom: 45px;
left: 50%;
transform: translateX(-50%);
}
.dots span{
margin: 0 4px;
width: 8px;
height: 8px;
display: inline-block;
border-radius: 100%;
background: #000;
opacity: .2;
text-indent: -9999px;
}
span.active {
width: 28px!important;
background: #5e7ce0!important;
border-radius: 5px!important;
opacity: 1;
}
第一步、创建函数,通过修改内层容器“box” 的transform: translateX(?px)来切换显示图片。
let boxDom = document.querySelector('.box');
function changeBoxDomStyle(offset, duration = '0ms') {
boxDom.style.transform = `translateX(${
offset}px)`;
boxDom.style.transitionDuration = duration;
}
第二步、计算当前所需偏移量,并调用设置偏移量函数carousel
,调用设置小点激活状态函数changeDotsStyle
技巧:
moveWith * index
boxDom.addEventListener('transitionend', () => {})
才能保证从8 -> 1副本能够顺利执行完了,再无痕迹的切换到真正的第一张图片。至于不放在这里面直接在赋值新的style会出什么副作用,就动手试试吧。let index = 1;
let imgLen = document.querySelectorAll('.slide').length;
let moveWith = document.querySelector('.slide').offsetWidth;
let currentOffset = moveWith;
const DEFAULT_DURATION = '300ms';
function carousel() {
//当切换到1副本的时候,迅速切换到真正的第一张图片
boxDom.addEventListener('transitionend', () => {
if(index === imgLen - 1){
index = 1;
currentOffset = moveWith;
changeBoxDomStyle(-currentOffset);
}
});
//
if(index < imgLen - 1){
index++;
changeDotsStyle();
currentOffset = moveWith * index;
changeBoxDomStyle(-currentOffset, DEFAULT_DURATION);
}
}
function changeDotsStyle() {
document.querySelector('.dot_item.active').classList.remove('active');
if(index === imgLen - 1){
dotItems[0].classList.add('active');
}else{
dotItems[index - 1].classList.add('active');
}
}
第三步、创建定时器调用函数carousel
const ANIMATION_INTERVAL = 3000;
function autoPlay() {
if(isAutoPlay){
return; }
isAutoPlay = true;
animate = setInterval(carousel, ANIMATION_INTERVAL);
}
给每个小点绑定点击事件,获取当前的索引,调用设置偏移量函数carousel。
注: 别忘了先停止定时器,再重起定时器
function dotsControl() {
for(dot of dotItems){
dot.addEventListener("click", (e) => {
stopAutoPlay();
index = e.target.getAttribute('data-dots-index') - 1;
carousel();
autoPlay();
});
}
}
function stopAutoPlay() {
if(!isAutoPlay){
return; }
isAutoPlay = false;
clearInterval(animate);
}
这是需求里面最难的。涉及多个鼠标事件,还有一个小坑
第一步、鼠标按下时,停止定时器。获取当前的X坐标currentX
, 修改isKeyDown
为true。
let isKeyDown = false;
boxDom.addEventListener('mousedown', (e) => {
stopAutoPlay();
isKeyDown = true;
isDragingImg = false;
currentX = e.clientX;
});
第二步、鼠标移动时计算与最初的currentX
相比坐标偏移量,调用changeBoxDomStyle
。
boxDom.addEventListener('mousemove', (e) => {
if(isKeyDown){
moveX = e.clientX - currentX;
let moveOffset = moveX - currentOffset;
changeBoxDomStyle(moveOffset);
isDragingImg = true;
}
});
第三步、鼠标抬起时,判断偏移量是否超过一半Math.abs(moveX) > minMoveOffset
,如果超过一半,就相应的往前moveX > 0
或者往后移动一个图片的距离。如果不够,那么就恢复原本的偏移量。保持之前的图片。最后恢复定时器。
let minMoveOffset = moveWith / 2;
document.onmouseup = function() {
isKeyDown = false;
if(Math.abs(moveX) > minMoveOffset) {
if(moveX > 0) {
//移动到前一个图片
if(index === 1){
//移动到真正的图片8
index = imgLen - 2;
currentOffset = index * moveWith;
}else{
index = index - 1;
currentOffset = currentOffset - moveWith;
}
}else {
//移动到后一个图片
currentOffset = currentOffset + moveWith;
index = index + 1;
if(index === imgLen -1) {
//移动到真正的图片1
currentOffset = moveWith;
index = 1;
}
}
changeDotsStyle();
}
changeBoxDomStyle(-currentOffset, '300ms');
autoPlay();
}
第四步、解决小坑
写完以上代码,你会发现mousemove只能获取几个值,会有个缩小版的图片跟着鼠标。这个时候需要禁止图片拖拽。
function stopImgDrag() {
let slideImgs = document.querySelectorAll('img.slide');
for (img of slideImgs) {
img.ondragstart = () => {
return false;
}
}
}
第五步、在拖拽的时候禁止 a 标签的跳转
function linkClickContr() {
let aNodeList = document.querySelectorAll('.slideWrapper > a');
for(let i of aNodeList)
i.addEventListener('click', (e) => {
if(isDragingImg) {
//isDragingImg在mousemove里面设置成了true
e.preventDefault();
}
});
}
监听window的resize,重设偏移量
window.addEventListener("resize", () => {
stopAutoPlay();
moveWith = document.querySelector('.slide').offsetWidth;
minMoveOffset = moveWith / 2;
currentOffset = index * moveWith;
changeBoxDomStyle(-currentOffset);
autoPlay();
}, false);
直接复制,存成xxx.html就可以看效果了。希望对路过的小伙伴有所帮助。也欢迎留言一起探讨技术的疑难杂症。因为用的是华为线上的图片。如果后面来的盆友发现加载有问题,随便上网找几张图替换就可以了。
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>轮播图title>
head>
<style type="text/css">
*{
box-sizing: border-box;
}
body, ul{
margin: 0;
padding: 0;
font-family: PingFang SC,PingFangSC-Regular,Helvetica Neue,Microsoft YaHei Regular,Microsoft YaHei,宋体,"sans-serif";
font-size: 16px;
}
.page{
overflow: hidden;
position: relative;
}
.box{
display: flex;
}
.slideWrapper{
width: 100%;
height: 100%;
flex-shrink: 0;
}
img{
width: 100%;
height: 100%;
}
.dots{
position: absolute;
bottom: 45px;
left: 50%;
transform: translateX(-50%);
}
.dots span{
margin: 0 4px;
width: 8px;
height: 8px;
display: inline-block;
border-radius: 100%;
background: #000;
opacity: .2;
text-indent: -9999px;
}
span.active {
width: 28px!important;
background: #5e7ce0!important;
border-radius: 5px!important;
opacity: 1;
outline: none;
}
.section_wrapper {
width: 1200px;
margin: 0 auto ;
position: relative;
top: -25px;
background: #fff;
box-shadow: 0 0 20px 0 rgba(41,48,64,.1);
border-radius: 5px;
padding: 30px;
display: flex;
align-items: center;
}
.section_leftTxt {
padding-right: 40px;
color: #5e6678;
line-height: 32px;
}
.section_rightLink {
text-decoration: none;
height: 32px;
line-height: 32px;
min-width: 96px;
background-color: #5e7ce0;
color: #fff;
font-size: 14px;
border-radius: 16px;
text-align: center;
}
.section_rightLink:hover,
.section_rightLink:focus {
background-color: #526ecc;
}
style>
<script type="text/javascript">
let index = 1;
let moveWith, dotItems, animate, currentX, moveX, currentOffset, minMoveOffset, imgLen, boxDom, isAutoPlay;
let isKeyDown = false;
let isDragingImg = false;
const DEFAULT_DURATION = '300ms';
const ANIMATION_INTERVAL = 3000;
window.onload = function() {
//initialize - set variables value
imgLen = document.querySelectorAll('.slide').length;
moveWith = document.querySelector('.slide').offsetWidth;
currentOffset = moveWith;
minMoveOffset = moveWith / 2;
dotItems = document.querySelectorAll('.dot_item');
boxDom = document.querySelector('.box');
//initailize - switch to first slide and make slide 8 backup visible
changeBoxDomStyle(-moveWith);
document.querySelectorAll('.slideWrapper')[0].style.visibility = 'visible';
autoPlay();
dotsControl();
mouseMoveImg();
stopImgDrag();
linkClickContr();
//reset parameter and offsetX when window resize
window.addEventListener("resize", () => {
stopAutoPlay();
moveWith = document.querySelector('.slide').offsetWidth;
minMoveOffset = moveWith / 2;
currentOffset = index * moveWith;
changeBoxDomStyle(-currentOffset);
autoPlay();
}, false);
}
function changeBoxDomStyle(offset, duration = '0ms') {
boxDom.style.transform = `translateX(${
offset}px)`;
boxDom.style.transitionDuration = duration;
}
function carousel() {
boxDom.addEventListener('transitionend', () => {
if(index === imgLen - 1){
index = 1;
currentOffset = moveWith;
changeBoxDomStyle(-currentOffset);
}
});
if(index < imgLen - 1){
index++;
changeDotsStyle();
currentOffset = moveWith * index;
changeBoxDomStyle(-currentOffset, DEFAULT_DURATION);
}
}
function dotsControl() {
for(dot of dotItems){
dot.addEventListener("click", (e) => {
stopAutoPlay();
index = e.target.getAttribute('data-dots-index') - 1;
carousel();
autoPlay();
});
}
}
function stopAutoPlay() {
//only auto play active then clearInterval in case extra action
if(!isAutoPlay){
return; }
isAutoPlay = false;
clearInterval(animate);
}
function autoPlay() {
//only auto play stop then setInterval in case extra action
if(isAutoPlay){
return; }
isAutoPlay = true;
animate = setInterval(carousel, ANIMATION_INTERVAL);
}
function stopImgDrag() {
let slideImgs = document.querySelectorAll('img.slide');
for (img of slideImgs) {
img.ondragstart = () => {
return false;
}
}
}
function changeDotsStyle() {
document.querySelector('.dot_item.active').classList.remove('active');
if(index === imgLen - 1){
dotItems[0].classList.add('active');
}else{
dotItems[index - 1].classList.add('active');
}
}
function mouseMoveImg() {
boxDom.addEventListener('mousedown', (e) => {
stopAutoPlay();
isKeyDown = true;
isDragingImg = false;
currentX = e.clientX;
});
boxDom.addEventListener('mousemove', (e) => {
if(isKeyDown){
moveX = e.clientX - currentX;
let moveOffset = moveX - currentOffset;
changeBoxDomStyle(moveOffset);
isDragingImg = true;
}
});
document.onmouseup = function() {
isKeyDown = false;
if(Math.abs(moveX) > minMoveOffset) {
if(moveX > 0) {
//move back previous img
if(index === 1){
//back to slide 8
index = imgLen - 2;
currentOffset = index * moveWith;
}else{
index = index - 1;
currentOffset = currentOffset - moveWith;
}
}else {
//move forward next img
currentOffset = currentOffset + moveWith;
index = index + 1;
if(index === imgLen -1) {
//move forward to slide 1
currentOffset = moveWith;
index = 1;
}
}
changeDotsStyle();
}
changeBoxDomStyle(-currentOffset, '300ms');
autoPlay();
}
}
//stop link redirect when customer use mouse move
function linkClickContr() {
let aNodeList = document.querySelectorAll('.slideWrapper > a');
for(let i of aNodeList)
i.addEventListener('click', (e) => {
if(isDragingImg) {
e.preventDefault();
}
});
}
script>
<body>
<div class="page">
<div class="box">
<div class="slideWrapper" style="visibility: hidden;"><a href="https://developer.huaweicloud.com/activity/full-stack/java-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-8.d14241daf518717981c6.jpg" alt="8"/>a>div>
<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/videos/102379" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-1.fdbf82d4c2ca7fad3225.jpg" alt="1"/>a>div>
<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/forum/thread-39737-1-1.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-2.64b1407e7a8db89d6cf2.jpg" alt="2"/>a>div>
<div class="slideWrapper"><a href="https://mp.weixin.qq.com/s/YRfQ41-IoXZtkj-i27US_w" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-3.ce76c93c7a8a149ce2a2.jpg" alt="3"/>a>div>
<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/full-stack/web-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-4.4ac0f6534a11844638e4.jpg" alt="4"/>a>div>
<div class="slideWrapper"><a href="https://classroom.devcloud.huaweicloud.com/joinclass/bb9a534bd1bd49e191911bf72b815b91/1" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-5.0e9bb68d425a4a5c3b18.jpg" alt="5"/>a>div>
<div class="slideWrapper"><a href="https://classroom.devcloud.huaweicloud.com/joinclass/2769fd5d78d14f2f98e80ff3d460c925/1" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-6.80cafe61bcb20a98eb1e.jpg" alt="6"/>a>div>
<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/reading/reading_pragmatic.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-7.d315f5241113c1fd9035.jpg" alt="7"/>a>div>
<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/full-stack/java-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-8.d14241daf518717981c6.jpg" alt="8"/>a>div>
<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/videos/102379" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-1.fdbf82d4c2ca7fad3225.jpg" alt="1"/>a>div>
div>
<div class="dots">
<span class="dot_item active" tabindex="0" role="button" aria-label="Go to slide 1" data-dots-index="1">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 2" data-dots-index="2">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 3" data-dots-index="3">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 4" data-dots-index="4">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 5" data-dots-index="5">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 6" data-dots-index="6">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 7" data-dots-index="7">span>
<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 8" data-dots-index="8">span>
div>
div>
<section class="section_wrapper">
<div class="section_leftTxt">
Classroom是基于华为云的云上软件教学服务,支持高校师生实现备课、上课、作业、考试、实验、实训等全教学流程的线上教学,提供多类习题自动判题、企业级DevOps实训、免费在线习题库等众多高级特性辅助进行数字化教学转型
div>
<a href="https://classroom.devcloud.huaweicloud.com/platform" target="_blank" class="section_rightLink">开始上课a>
section>
body>
html>