Ⅰ- 壹 - 无缝轮播图展示
一功能说明:
利用模块开发引入html;导入模块 需要实例化对象进行操作
- 可以创建多个轮播图,互不干扰
- 第一种写法中,只有用到Utils.js 工具类
- 第二种写法中,用到Utils.js 工具类和Loadlmage.js 预加载
Ⅱ - 贰 - HTML结构
Ⅲ - 叁 - 无缝轮播图原理
Ⅳ - 肆 - 代码区域
Carousel.js实现类思维导图
一 第一种写法
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
/* 设置rem格式 */
html{
font-size: 100px;
}
body{
font-size: 16px;
margin: 0;
padding: 0;
border: 1px solid seagreen;
}
</style>
</head>
<body>
</body>
<script type="module">
//引用Carouse.js
import Carousel from "./js/Carousel.js";
//轮播图片储存在一个数组內,
let arr = ["a.png", "b.png", "c.png", "d.png", "e.png"];
//实例化Carouse.js
let carousel=new Carousel(arr,"./img/",1200,400);
carousel.appendTo("body");
animation();
function animation(){
requestAnimationFrame(animation);
Carousel.update();
}
window.addEventListener("resize",);
function resizeHandler(e){
document.documentElement.style.fontSize=(document.documentElement.clientWidth/screen.width)*100+"px"
}
</script>
</html>
Carousel.js实现类
import Utils from "./Utils.js";//引用Utils.js
export default class Carousel {
position = 0;//储存轮播图数组的下标
direction = "left";//储存左右按钮的别名
bnList = [];//储存按钮的点击
moveBool = false;//按钮点击的状态
autoBool = false;//自动轮播
speed =30;//轮播动画速度 为0.03rem
x = 0;
static carouselList = [];//储存类
//list:轮播图的数组 basePath:图片路径 w :轮播图父容器的宽 h:轮播图父容器的高
constructor(list,basePath,w,h) {
// 设rem
// 预加载
// 预加载完成创建轮播图
//实参设为全局变量 this.w,this.h
this.w = w;
this.h = h;
//单例模式 解决了div的创建多次 执行结果轮播图父容器createCarousel()赋值给 this.elem
this.elem = this.createCarousel();
Carousel.carouselList.push(this);
Utils.reload(list, _list => this.loadFinish(_list), basePath);//list 数组 callback回调函数 basePath图片路径
}
//预加载回调函数
loadFinish(_list) {
this.list = _list;//
this.createImgCon(_list);//
this.createDot(_list);
this.createBn();
this.preChange();
}
//创建轮播图的父容器 div
createCarousel() {
if (this.elem) return this.elem;//单例模型 如果有值就把当前这个返回 不执行下面代码
//实例化Utils.js ce()是类似于 Object.assign Utils.js 的方法ce()使用
let div = Utils.ce("div");//创建一个div 返回给div这个变量
//箭头函数会改变this指向Carousel 在mouseHanlder()函数里 this指向都是Carousel
// div.addEventListener("mouseenter", e => this.mouseHanlder(e));//当鼠标指针穿过元素时,会发生 mouseenter 事件
// div.addEventListener("mouseleave", e => this.mouseHanlder(e));//当鼠标指针离开被选元素时,会发生 mouseleave 事件。
return div;
}
//把轮播图的容器插入到哪个容器内的方法 轮播图父容器的父容器 carousel.appendTo("body");
appendTo(parent) {
//constructor 属性返回对创建此对象的数组函数的引用。
//如果是字符串就获取这个 parent.constructor===String判断是不是字符串
if (parent.constructor === String) parent = document.querySelector(parent);
//this.elem:div 把创建的div追加到parent(body)元素 div为body的子元素
parent.appendChild(this.elem);
//this.w,this.h 为传过来的参数 分别为1200 400 三目运算如果有值就进行设置 没有值获取元素的宽度
this.contentWidth = this.w ? Carousel.getRem(this.w) : Carousel.getRem(parent.offsetWidth);
this.contentHeight = this.h ? Carousel.getRem(this.h) : Carousel.getRem(parent.offsetWidth / 3);//高度为 容器的3/1
//给div 这个元素添加样式 div样式也为div父容器的宽度和高度
Object.assign(this.elem.style, {
width: this.contentWidth + "rem",
height: this.contentHeight + "rem",
position: "relative",
overflow: "hidden",
border:"1px solid seagreen"
});
}
//显示区域的div的轮播图 的父容器
createImgCon(list) {
//遍历轮播图数组 给每个图片添加宽高 父容器宽高度
list.forEach(item => {
Object.assign(item.style, {
width: this.contentWidth + "rem",
height: this.contentHeight + "rem"
})
});
//轮播图的父容器 的宽度是2倍
this.imgCon = Utils.ce("div", {
width: this.contentWidth * 2 + "rem",
height: this.contentHeight + "rem",
position: "absolute"
});
//把第一张图片插入到 轮播图父容器里面
this.imgCon.appendChild(list[this.position]);
// 添加到轮播图父容器的父容器
this.elem.appendChild(this.imgCon);
}
//创建小圆点的方法 _list预加载完成后返回的数组
createDot(_list) {
//创建一个与轮播图父容器div同级的 ul容器
this.dot = Utils.ce("ul", {
listStyle: 'none',
margin: '0px',
position: 'absolute',
bottom: Carousel.getRem(20) + "rem" //20/100 0.2rem
});
//局部变量 w
var w = 0;
//根据图片数量 创建小圆点
for (let i = 0; i < _list.length; i++) {
let li = Utils.ce("li", {
width: Carousel.getRem(20) + "rem",
height: Carousel.getRem(20) + "rem",
borderRadius: "50%",
float: "left",
border: "2px solid #FF0000",
//第一次进入循环 下表为0 的小圆点 设置
marginLeft: i === 0 ? "0rem" : Carousel.getRem(10) + "rem" //第一个小圆点 left为0
});
//每次循环都会累加20,第一个不累加 , 0 20 40 60 80 100 120
w += 20+(i === 0 ? 0 : 10);
this.dot.appendChild(li);
}
//小圆点的左间隔 设置
this.dot.style.left = (this.contentWidth - Carousel.getRem(w)) / 2 + "rem";
this.dot.addEventListener("click", e => this.dotClickHandler(e));//ul添加事件 给每个小圆点绑定事件
this.elem.appendChild(this.dot);
}
//左右按钮
createBn() {
//巡皇两次创建两个div
for (let i = 0; i < 2; i++) {
//Utils 方法创建
let bn = Utils.ce("div", {
//getRem() 方法是从前端获取fontzise的大小进行转换为 rem
//设置左右按钮的 样式
width: Carousel.getRem(30) + "rem",
height: Carousel.getRem(60) + "rem",
userSelect: "none",
fontWeight: "bold",
fontSize: Carousel.getRem(40) + "rem",
lineHeight: Carousel.getRem(60) + "rem",
textAlign: "center",
position: "absolute",
backgroundColor: "rgba(0,0,0,0.3)",
top: (this.contentHeight - Carousel.getRem(60)) / 2 + "rem",
//利用循环 当第一次进入循环 的时候 i=0这时候设置为左边
left: i === 0 ? Carousel.getRem(20) + "rem" : "none",
// i=这时候设置为左边
right: i === 1 ? Carousel.getRem(20) + "rem" : "none",
//左右按钮的 阴影样式
boxShadow: i === 0 ? "2px 2px 2px #000" : "-2px 2px 2px #000",
}, {
//同理根据循环的累加值i 添加标签属性 DOMtextContent 属性设置或返回指定节点的文本内容,以及它的所有后代。
textContent: i === 0 ? "<" : ">"
});
this.bnList.push(bn);//添加到数组bnList
bn.addEventListener("click", e => this.bnClickHandler(e));//绑定点击事件
//把左右按钮追加到参数elem 后面
this.elem.appendChild(bn);
}
}
//ul事件执行 小圆点点击事件 点击到那个小圆点跳转到哪个图上面
dotClickHandler(e) {
if (this.moveBool) return;//为falst 的时候 跳出
//获取当前点击元素的创建此对象的数组函数的引用。是不是HTMLLIElement
if (e.target.constructor !== HTMLLIElement) return;
//ul的子元素li转换为数组对象 根据indexOf查找当前点击的元素 查到之后返回索引值
var index=Array.from(this.dot.children).indexOf(e.target);
//position 轮播图的索引值 如果当前点击的索引值等于储存的position值就跳出
if (index == this.position) return;
//当前的点击的 li 大于 储存的position direction:储存左右按钮的别名
if (index > this.position) {
this.direction = "left";
} else {
this.position = "right";
}
this.position = index;
this.createNextImg();
}
//左右按钮事件执行方法
bnClickHandler(e) {
if (this.moveBool) return;//控制轮播图的移动 为falst 的时候 进入
//判断监听事件的这个元素的文本内容
if (e.currentTarget.textContent === "<") {
//设置为右
this.direction = "right";
//position减一 数组位置
this.position--;
//如果position的的值一直触发减到0时 position赋值为储存图片的最大值
if (this.position <= 0) this.position = this.list.length - 1;
} else {
this.direction = "left";
this.position++;
if (this.position > this.list.length - 1) this.position = 0;
}
this.createNextImg();//执行方法 点击按钮图片切换方法
}
//点击按钮图片切换方法 创建下一张图片
createNextImg() {
//判断当前储存左右按钮别名的值direction
if (this.direction === "left") {
this.imgCon.appendChild(this.list[this.position]);
this.imgCon.style.left = "0rem";
this.x = 0;
} else {
//获取第一个元素节点:firstElementChild
//insertBefore() 方法可在已有的子节点前插入一个新的子节点。
//imgCon:轮播图的父容器 的宽度是2倍
this.imgCon.insertBefore(this.list[this.position], this.imgCon.firstElementChild);
this.x = -this.contentWidth;
this.imgCon.style.left = this.x + "rem";
}
this.moveBool = true;
this.preChange();
}
//轮播图小圆点,点击的样式变化
preChange() {
//this.pre 当前点击小图标
//第一次不进入
if(this.pre){
this.pre.style.backgroundColor="rgba(255,0,0,0)";
}
//this.dot.children[this.position] 根据position 轮播图的索引值 设置 小图标的背景
this.pre=this.dot.children[this.position];
//当前点击的 小图标背景设置为
this.pre.style.backgroundColor="rgba(255,0,0,1)";
}
update(){
this.moveImg();
}
//ul 移动时 的变化 当单击左边时 在
moveImg(){
if(!this.moveBool) return;
if(this.direction==="left"){
//左按钮点击后
this.x-=Carousel.getRem(this.speed);
this.imgCon.style.left=this.x+"rem";
if(this.x<=-this.contentWidth){
this.moveBool=false;
//移除imgCon的第一个元素
this.imgCon.firstElementChild.remove();
this.x=0;
this.imgCon.style.left=this.x+"rem";
}
}else{
this.x+=Carousel.getRem(this.speed);
this.imgCon.style.left=this.x+"rem";
if(this.x>=0){
this.moveBool=false;
this.imgCon.lastElementChild.remove();
this.x=0;
this.imgCon.style.left=this.x+"rem";
}
}
}
//防止多次创建
static update(){
for (let i = 0; i < Carousel.carouselList.length; i++) {
Carousel.carouselList[i].update();
}
}
//静态方法 转换为rem
static getRem(size) {
//获取css中样式 fontSize:(100)在html设置增加灵活性 getComputedStyle css获取样式仅可读
var fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
//传入的实参除fontSize设置的值
return size / fontSize;
}
}
第二种写法
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html {
font-size: 100px;
}
body {
font-size: 16px;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<script type="module">
import Carousel from "./js/Carousel.js"
window.addEventListener("resize",resizeHandler);//重新修改大小事件 就是窗口缩小
function resizeHandler(e){
document.documentElement.style.fontSize=document.documentElement.clientWidth*(100/screen.width)+"px";
}
animation();//
function animation(){
requestAnimationFrame(animation);
Carousel.UPDATE();
}
// 1.
//图片数组
var arr=["./img/a.png","./img/b.png","./img/c.png","./img/d.png","./img/e.png","./img/left.png","./img/right.png"];
//
let carousel=new Carousel(arr);
carousel.appendTo("body");//追加到body
carousel.setWH(true);//全屏显示
// 2.
var arr1=["./img/a.jpeg","./img/b.jpeg","./img/c.jpeg","./img/d.jpeg","./img/e.jpeg","./img/left.png","./img/right.png"];
let carousel1=new Carousel(arr1);
carousel1.appendTo("body");//追加到body
carousel1.setWH(600,200);//设置宽高
</script>
</body>
</html>
Carousel.js
import Loadlmage from "./Loadlmage.js";
import Utils from "./Utils.js";
export default class Carousel {
imgList;//图片数组包括左右按钮
w;//外部容器宽
h;//外部容器高
bnlist;//存放左右按钮的图标
list;//存放每一张图片 是个数组
imageCon;// 存放图片容器图片的二倍容器
parent;//存放轮播图的父容器
dot;//ui 小圆点的父元素
dotList = [];//全局储存创建的每个小圆点
pos = 0;//存放图片的下标
direction = "";//存放左右按钮的 名字 left right
bool = false;//控制开关 轮播动画
x = 0;//轮播图片的横坐标
speed = 0.5;//偏移量
autoBool = false;//控制自动轮播的动画
time = 200;//防抖的初始值
pre;//控制小圆点的选中
static carouselList = [];//存放这个轮播图对象
//参数为 图片数组
constructor(_imgList) {
this.imgList = _imgList;//图片数组到全局
this.carousel = this.createCarousel();//创建外容器
new Loadlmage(_imgList, list => this.finishHandler(list))//预加载 第一个参数是数组 第二个参数是回调函数
Carousel.carouselList.push(this);//当前类添加到数组
console.log(Carousel.carouselList)
}
//预加载函数 预加载完成对图片等一些操作
finishHandler(_list) {
this.bnlist = _list.splice(-2);//从倒数第二位截取到尾部 获取左右按钮的图片 存为全局
//this.w 没有值的时候 获取第一张图的宽高作为轮播图的宽高
//既 setWH() 参数为false
if (!this.w) {
this.w = Carousel.getRem(_list[0].width);
this.h = Carousel.getRem(_list[0].height);
}
//到此为止w和h正式全部确认完毕 所有都已将加载完毕
//给每一张图片添加宽高 计算完毕的 w h
this.list = _list.map(item => {
item.style.width = this.w + "rem";
item.style.height = this.h + "rem";
return item;
})
//设置外容器的 宽高
Object.assign(this.carousel.style, {
width: this.w + "rem",
height: this.h + "rem"
})
//
this.createImageCon();//创建图片的二倍容器
this.createBn();//左右按钮
this.createDot();//轮播图下的 小圆点
this.changePre();//点击小圆点的切换
}
//创建外容器
createCarousel() {
var carousel = Utils.ce("div", {
position: "relative",
margin: "auto",
overflow: "hidden",
left: 0,
right: 0,
backgroundColor: "rgba(255,0,0,0.1)",
})
//添加鼠标事件 鼠标移入和鼠标进入 和鼠标移出 用于轮播自动轮播 鼠标悬停停止
carousel.addEventListener("mouseenter", e => this.mouseHandler(e));
carousel.addEventListener("mouseleave", e => this.mouseHandler(e));
return carousel;
}
// 创建图片的二倍容器
createImageCon() {
this.imageCon = Utils.ce("div", {
width: this.w * 2 + "rem",
height: this.h + "rem",
position: "absolute",
left: 0,
})
//先把第一个图片添加在图片的容器内
this.imageCon.appendChild(this.list[0]);
//把整个容器添加在外容器中 详细看图解
this.carousel.appendChild(this.imageCon);
}
//左右按钮
createBn() {
//设置左右按钮 和垂直居中
this.bnlist.forEach((item, index) => {
Object.assign(item.style, {
position: "absolute",
left: index === 0 ? "20px" : "none",
right: index === 1 ? "20px" : "none",
top: (this.h - item.height / 100) / 2 + "rem"
});
item.addEventListener("click", e => this.clickBnHandler(e));//绑定点击事件
this.carousel.appendChild(item);//添加到外容器
})
}
//点击左右按钮触发事件
clickBnHandler(e) {
//点击左边的时候进行++ 超过图片数组的时候赋值0 反之。。。
if (this.bnlist.indexOf(e.currentTarget) === 0) {
this.pos--;
if (this.pos < 0) this.pos = this.list.length - 1;
this.direction = "right";
} else {
this.pos++;
if (this.pos > this.list.length - 1) this.pos = 0;
this.direction = "left";
}
this.createNextImage();//加载下一张图片
}
// 加载下一张图片
createNextImage() {
if (this.direction === "left") {
this.imageCon.appendChild(this.list[this.pos]);//
this.imageCon.style.left = "0rem";//移动
this.x = 0;
} else {
this.imageCon.insertBefore(this.list[this.pos], this.imageCon.firstChild);
this.imageCon.style.left = -this.w + "rem";
this.x = -this.w;
}
this.bool = true;//自动轮播
this.changePre();//切换小圆点的变化
}
//创建轮播图下的 小圆点
createDot() {
this.dot = Utils.ce("ul", {
listStyle: "none",
margin: "0px",
padding: "0px",
position: "absolute",
bottom: "0.2rem"
})
//根据图片数组的个数创建小圆点的个数
this.list.forEach((item, index) => {
var li = Utils.ce("li", {
width: "0.15rem",
height: "0.15rem",
backgroundColor: "rgba(255,0,0,0)",
border: "1px solid #FF0000",
borderRadius: "0.15rem",
float: "left",
marginLeft: index === 0 ? 0 : "0.1rem"
}, this.dot);
this.dotList.push(li);//全局储存创建的每个小圆点
});
this.carousel.appendChild(this.dot);//把UI追加到外容器
this.dot.style.left = (this.w - Carousel.getRem(this.dot.offsetWidth)) / 2 + "rem";
this.dot.addEventListener("click", e => this.clickDotHandler(e));//点击小圆点触发事件
}
//点击小圆点触发事件
clickDotHandler(e) {
if (e.target.constructor !== HTMLLIElement) return;
var index = this.dotList.indexOf(e.target);
if (index === this.pos) return;
this.direction = index > this.pos ? "left" : "right";//
this.pos = index;
this.createNextImage();//加载下一张图片
}
//小圆点样式切换
changePre() {
if (this.pre) {
this.pre.style.backgroundColor = "rgba(255,0,0,0)";
}
this.pre = this.dotList[this.pos];
this.pre.style.backgroundColor = "rgba(255,0,0,0.6)";
}
//控制自动轮播 防抖鼠标离开200次之后开始自动轮播
// 控制autoBool这个变量
mouseHandler(e) {
if (e.type === "mouseenter") {
this.autoBool = false;
this.time = 200;
} else {
this.autoBool = true;
}
}
//动画执行的函数
update() {
this.imgMove();
this.autoPlay();
}
//静态方法
static UPDATE() {
for (var i = 0; i < Carousel.carouselList.length; i++) {
Carousel.carouselList[i].update();
}
}
//图片移动的动画
//
imgMove() {
if (!this.bool) return;
if (this.direction === "left") {
this.x -= this.speed;
if (this.x <= -this.w) {
this.x = 0;
this.bool = false;
this.imageCon.firstElementChild.remove();
}
this.imageCon.style.left = this.x + "rem";
} else {
this.x += this.speed;
if (this.x >= 0) {
this.x = 0;
this.bool = false;
this.imageCon.lastElementChild.remove();
}
this.imageCon.style.left = this.x + "rem";
}
}
//自动轮播的函数
autoPlay() {
if (!this.autoBool) return;
this.time--;
if (this.time > 0) return;
this.time = 200;
var evt = new MouseEvent("click");
this.bnlist[1].dispatchEvent(evt);
}
//外部调用 设置轮播图的大小
setWH(_w, _h) {
// 如果没有添加到html就退出
if (!this.parent) return;
if (_w.constructor === Boolean) {
// 第一个值为true 的时候获取 this.parent 添加的元素的位置
//轮播图这个容器 宽为这个添加容器的宽 高为3/1
if (_w) {
var rect = this.parent.getBoundingClientRect();
this.w = Carousel.getRem(rect.width);//转换为 rem
this.h = this.w / 3;
}
} else if (_w.constructor === Number) {
//如果第一个参数是个数值型的就把参数转换成rem 存为全局变量
this.w = Carousel.getRem(_w);
this.h = Carousel.getRem(_h);
}
}
//静态方法 转换rem
static getRem(size) {
//获取css中样式 fontSize:(100)在html设置增加灵活性 getComputedStyle css获取样式仅可读
var fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
//传入的实参除fontSize设置的值
return size / fontSize;
}
//外部执行 把这个轮播添加到哪个元素
appendTo(parent) {
//如果参数是个字符串,就从htnl页面获取
if (parent.constructor === String) parent = document.querySelector(parent);
//吧容器添加到外部参数的元素
parent.appendChild(this.carousel);
this.parent = parent;//把参数 存为全局变量
}
}
Utils.js工具类
1.预加载: list 数组 callback回调函数 basePath图片路径
方法:
//预加载 list 数组 callback回调函数 basePath图片路径
static reload(list,callback,basePath){
if(!list || list.length===0) return;//数组没传值 跳出 不执行下面内容
//list内容和路径拼接
if(basePath){
list=list.map(item=>basePath+item);
}
//创建图片
var img=new Image();
//给图片添加一个对象属性
img.data={
num:0,//用于累加
resultList:[],//储存每次循环之后的复制的img
list:list,//储存数组
callback:callback//储存回调函数
}
img.addEventListener("load",Utils.loadHandlers);//加载事件
img.src=list[0];//第一张图片的路径
}
//执行加载事件
static loadHandlers(e){
var data=e.currentTarget.data;//
data.resultList.push(e.currentTarget.cloneNode(false));
data.num++;
if(data.num>data.list.length-1){
e.currentTarget.removeEventListener("load",Utils.loadHandlers);
//回调函数 返回的是图片的加载顺序
data.callback(data.resultList);
e.currentTarget.data=null;
return;
}
e.currentTarget.src-data.list[data.num];
}
用法:
constructor(list,basePath,w,h){
// 设rem
// 预加载
// 预加载完成创建轮播图
this.w=w;
this.h=h;
this.elem=this.createCarousel();
// console.log( Carousel.carouselList);
Carousel.carouselList.push(this);
Utils.reload(list,_list=>this.loadFinish(_list),basePath);
}
loadFinish(_list){
this.list=_list;
this.createImgCon(_list);
this.createDot(_list);
this.createBn();
this.preChange();
}
2.添加css样式 的重构 三个参数分别是 标签 样式 属性
方法:
static ce(type,style,props){
let elem=document.createElement(type);
//如果属性是一个对象 遍历
if (style) {
for (let prop in style) {
elem.style[prop]=style[prop];
}
}
if(props){
for(let prop in props){
elem[prop]=props[prop];
}
}
return elem;
}
用法:
let bn = Utils.ce("div", {
//getRem() 方法是从前端获取fontzise的大小进行转换为 rem
//设置左右按钮的 样式
width: Carousel.getRem(30) + "rem",
height: Carousel.getRem(60) + "rem",
userSelect: "none",
fontWeight: "bold",
fontSize: Carousel.getRem(40) + "rem",
lineHeight: Carousel.getRem(60) + "rem",
textAlign: "center",
position: "absolute",
backgroundColor: "rgba(0,0,0,0.3)",
top: (this.contentHeight - Carousel.getRem(60)) / 2 + "rem",
//利用循环 当第一次进入循环 的时候 i=0这时候设置为左边
left: i === 0 ? Carousel.getRem(20) + "rem" : "none",
// i=这时候设置为左边
right: i === 1 ? Carousel.getRem(20) + "rem" : "none",
//左右按钮的 阴影样式
boxShadow: i === 0 ? "2px 2px 2px #000" : "-2px 2px 2px #000",
}, {
//同理根据循环的累加值i 添加标签属性 DOMtextContent 属性设置或返回指定节点的文本内容,以及它的所有后代。
textContent: i === 0 ? "<" : ">"
});
0 - 0 - 知识点:
一 通过抛发事件触发点击事件
var evt = new MouseEvent("click");
this.bnlist[1].dispatchEvent(evt);