封装一个属于自己的轮播图插件——左右切换版
上一篇文章 案例|原生手写一个轮播图——渐隐渐显版 还有很多不足,这里要非常感谢大佬 csdoker给出的宝贵意见和指导🙏,所以笔者决定重新完善一下轮播图的案例,打算做一个简易版的左右轮播图插件的封装;
一、插件封装前言(封装的基础)
思维导图
(一)、插件封装思想
1、敏捷化平台的构建思想
封装一款插件,首先第一件事情一定是分析,分析最后实现的效果和可以支持的配置项
2、依据:
- => 经验积攒
- => 研究别人优秀的插件😄
3、目的:
写插件的目的是方便后期的开发,而且需要让更多人使用(开源)
4、要求:
- -> 专业,我们写的插件性能要好(代码要好)
- -> 易用,别人使用起来非常的简便
- -> 强大,尽可能在代码简洁的情况下,支持更强大的效果
- -> 灵活,尽可能基于原生JS编写,不要依赖其他的类库或者内容(拿来即用主义)
- -> 扩展,可升级,升级的时候要考虑到低版本的向后兼容,而且不要和原始的版本差距太大
- ……等等
(二)、左右轮播图插件封装思想
插件 组件 类库 等这些东西封装 基本上都要基于面向对象思想
- 面向对象的思想能够保证每一个实例之间没有关联
- 面向对象的最主要目的就是:插件组件封装
我们封装的插件是一个类
- 基于插件每一次调用实现的轮播图 都是创建类的一个实例
对于轮播图来说:
- 1.基本核心信息应该都是私有的
- 2.一些操作的方法应该是公用的
(三)、插件封装中的形参处理
方法一:设置为形参,一个个传递
特点:
- -> 传递实参的顺序必须和形参设置顺序保持一致
- -> 不好给形参设置默认值
- -> 中间的一些项如果不传递,会把后面传递的项错位
使用场景:
- 一般这种情况只应用于参数特别少的情况下(一般不超过两个)
方法二:基于对象键值对的方式处理
特点:
- -> 可以传递,也可以不传递
- -> 传递的顺序也随意,因为都是基于属性名标记好的,只要属性和值对应,顺序是无所谓的
- -> 方便扩展
- -> 不传递的信息我们给其设置默认值
使用场景:
- 对于传递多个参数的情况下,我们一般基于对象键值对的方式处理
二、轮播图封装——HTML
这一部分我们可以写一点架子出来,用户使用时直接CV过来,更换自己的图片,配置自己的一些细节内容即可; 仓库地址
<!-- 轮播图整体容器 -->
<div class="xiaozhima-container">
<!-- 所有图片容器 -->
<div class="xiaozhima-wrapper">
<!-- 每个图片容器 -->
<div class="xiaozhima-slide">
<!-- 插入您的图片 -->
<img src=" " alt="">
</div>
<div class="xiaozhima-slide">
<img src=" " alt="">
</div>
<div class="xiaozhima-slide">
<img src=" " alt="">
</div>
<div class="xiaozhima-slide">
<img src=" " alt="">
</div>
</div>
<!-- 分页器容器 -->
<div class="xiaozhima-pagination"></div>
<!-- 左右按钮容器 -->
<a href="javascript:;" class="xiaozhima-arrow-prev"></a>
<a href="javascript:;" class="xiaozhima-arrow-next"></a>
</div>
复制代码
三、轮播图封装——CSS
这一部分我们也是写一点架子出来,把我们需要实现的内容配置好,细节样式由用户自己指定即可
1、使用方式:
- 引入命名为
banner-plugin.css
的样式表即可 ;仓库地址 (大家也可以自己创建一个CSS文件,把代码直接粘贴进去即可);
2、用户需要配置的内容:
轮播图整体容器的:
- width
- height
插入您的图片中的:(可不更改只传图片即可)
- 图片及图片的样式
3、需要在JS中动态计算的样式
所有图片容器的:
- width :根据SLIDE的个数(含克隆的)* CONTAINER容器的宽度
- transition:是在JS中设置的
- left:初始展示哪一个SLIDE,LEFT值就应该是计算好的
每个图片容器的:
- width :需要和用户传递的CONTAINER的宽度保持一致(JS中处理)
左右按钮:(北京图片转为BASE64编码)
- 插件中的样式资源最好不要使用图片(因为这样需要用户额外的导入,不利于插件的推广和使用) => 需要使用图片,我们最好把图片设置为BASE64编码
- 一个把图片转为BASE64编码的在线网站:tool.css-js.com/base64.html
4、具体的CSS代码
.xiaozhima-container {
/*
* 需要用户后期自己配置的样式
* width
* height
*/
position: relative;
box-sizing: border-box;
width: 100%;
overflow: hidden;
}
.xiaozhima-container .xiaozhima-wrapper {
/*
* 需要在JS中动态计算的样式
* width 根据SLIDE的个数(含克隆的)* CONTAINER容器的宽度
* transition 是在JS中设置的
* left 初始展示哪一个SLIDE,LEFT值就应该是计算好的
*/
display: flex;
box-sizing: border-box;
position: absolute;
top: 0;
height: 100%;
}
.xiaozhima-container .xiaozhima-wrapper .xiaozhima-slide {
/*
* 需要用户后期自己配置的样式
* width 和CONTAINER的宽度保持一致(JS中处理)
*/
box-sizing: border-box;
height: 100%;
overflow: hidden;
}
.xiaozhima-container .xiaozhima-wrapper .xiaozhima-slide img {
display: block;
width: 100%;
height: 100%;
}
.xiaozhima-container .xiaozhima-pagination {
position: absolute;
bottom: 10px;
left: 50%;
z-index: 999;
transform: translateX(-50%);
padding: 3px 6px;
background: rgba(255, 255, 255, .3);
font-size: 0;
border-radius: 10px;
}
.xiaozhima-container .xiaozhima-pagination span {
display: inline-block;
margin: 0 6px;
width: 10px;
height: 10px;
border-radius: 50%;
background: lightblue;
cursor: pointer;
}
.xiaozhima-container .xiaozhima-pagination span.active {
background: lightcoral;
}
.xiaozhima-arrow-prev,
.xiaozhima-arrow-next {
display: none;
position: absolute;
z-index: 999;
top: 50%;
margin-top: -22.5px;
width: 30px;
height: 45px;
/* 插件中的样式资源最好不要使用图片(因为这样需要用户额外的导入,不利于插件的推广和使用) => 需要使用图片,我们最好把图片设置为BASE64编码 */
background: url("data:image/png;base64,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") no-repeat 0 0;
}
.xiaozhima-arrow-prev {
left: 0;
}
.xiaozhima-arrow-next {
right: 0;
background-position: -50px 0;
}
.xiaozhima-container:hover .xiaozhima-arrow-prev,
.xiaozhima-container:hover .xiaozhima-arrow-next {
display: block;
}
复制代码
四、轮播图封装——JS(重点)
(一)、第一步——功能及配置项分析
1、目的:
- 封装一个
bannerPlugin
方法,可以让指定的容器实现出左右运动版的轮播图效果;
2、使用语法:
- bannerPligin([container],[options]) ;
3、参数说明:
- container:容器标签
- options:需要配置项(这里用户配置的参数较多,所以我们采用对象数据类型)
参数 options 配置项详解
(二)、第二步——参数初始化
上面我们说过了参数的处理方式有两种,我们这里选择基于对象健值对的方式处理;
这里用到了我们昨天铺垫的深比较、循环迭代方法(深克隆 VS 浅克隆|深比较 VS 浅比较|回调函数),还有数据类型检测的方法,文章JS中数据类型检测四种方式的优缺点 的末尾提到过,我把他作为工具包先放在这里
1、工具包
仓库地址 大家也可以直接CV
/* 工具包 */
(function () {
// 数据类型检测
let class2type = {};
["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol"].forEach(item => {
class2type["[object " + item + "]"] = item.toLowerCase();
});
function toType(obj) {
if (obj == null) return obj + "";
return typeof obj === "object" || typeof obj === "function" ?
class2type[class2type.toString.call(obj)] || "object" :
typeof obj;
}
// 检测是否为函数
function isFunction(obj) {
return typeof obj === "function" && typeof obj.nodeType !== "number";
}
// 检测window
function isWindow(obj) {
return obj != null && obj === obj.window;
}
// 数组/类数组
function isArrayLike(obj) {
var length = !!obj && "length" in obj && obj.length,
type = toType(obj);
if (isFunction(obj) || isWindow(obj)) return false;
return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}
// 循环迭代
function _each(obj, callback, context = window) {
if (/^(ARRAY|OBJECT)$/i.test(obj.constructor)) {
obj = _cloneDeep(obj);
}
if (isArrayLike(obj)) {
for (let i = 0; i < obj.length; i++) {
let res = callback && callback.call(context, obj[i], i);
if (res === false) break;
if (res !== undefined) obj[i] = res;
}
} else {
for (let key in obj) {
if (!obj.hasOwnProperty(key)) break;
let res = callback && callback.call(context, obj[key], key);
if (res === false) break;
if (res !== undefined) obj[key] = res;
}
}
return obj;
}
// 深克隆
function _cloneDeep(obj) {
if (obj === null) return null;
if (typeof obj !== "object") return obj;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
let cloneObj = new obj.constructor;
for (let key in obj) {
if (!obj.hasOwnProperty(key)) break;
cloneObj[key] = _cloneDeep(obj[key]);
}
return cloneObj;
}
// 深比较
function _assignDeep(obj1, obj2) {
let obj = _cloneDeep(obj1);
for (let key in obj2) {
if (!obj2.hasOwnProperty(key)) break;
let v2 = obj2[key],
v1 = obj[key];
if ((v1 !== null && typeof v1 === "object") && (v2 !== null && typeof v2 === "object")) {
obj[key] = _assignDeep(v1, v2);
continue;
}
obj[key] = v2;
}
return obj;
}
['_each', '_cloneDeep', '_assignDeep', 'toType', 'isFunction', 'isWindow', 'isArrayLike'].forEach(item => {
window[item] = eval(item);
});
})();
复制代码
2、参数初始化的目的
- 配置默认的参数信息,当用户不传递对应参数时走默认信息
function bannerPlugin(container, options = {}) {
// 参数初始化:默认配置信息
let defaultParams = {
initialSlide: 0,
autoplay: 3000,
speed: 300,
pagination: {
el: '.xiaozhima-pagination',
clickable: true
},
navigation: {
nextEl: '.xiaozhima-arrow-next',
prevEl: '.xiaozhima-arrow-prev'
},
on: {
init() {},
transitionStart() {},
transitionEnd() {}
}
};
// 利用深比较,把用户传递进来的参数和默认配置合并(达到替换默认配置的效果)
options = _assignDeep(defaultParams, options);
// 判断用户传递进来的第一个参数是否为元素容器,支持直接传递标签
typeof container === "string" ? container = document.querySelector(container) : null;
if (!container || container.nodeType !== 1) {
throw new TypeError('container must be an element!');
}
return new Banner(container, options);
}
复制代码
(三)、第三步——配置每个轮播图的私有属性
1、利用类与实例的方式实现每个轮播图之间互不影响
- 用户在使用时,每个配置信息肯定是当前轮播图私有的,所以我们配置好的默认参数,应该是每一个实例的私有属性
class Banner {
constructor(container, options) {
// 把传递进来的信息都挂载到当前类的实例上
// 1.信息都作为他的私有属性(这样每一个实例之间互不影响)
// 2.挂载到实例上,以后在当前类的其它方法中,只要保证THIS是实例,都可以基于THIS.XXX获取和操作
_each(options, (item, key) => {
this[key] = item;
});
this.container = container;
this.activeIndex = this.initialSlide;
}
}
复制代码
(四)、第四步——配置公共方法(重点)
- 上面我们给每个轮播图实例都添加了私有属性,下面的公共方法才是重点内容
1、计算结构和样式函数
- 每一个轮播图的完成,必要的操作都是获取元素
computed() {
let {
container,
pagination,
navigation
} = this;
// 轮播图
this.wrapper = container.querySelector('.xiaozhima-wrapper');
this.slidesTrue = container.querySelectorAll('.xiaozhima-slide');
// 克隆第一张到容器的末尾
this.wrapper.appendChild(this.slidesTrue[0].cloneNode(true));
this.slides = container.querySelectorAll('.xiaozhima-slide');
// 分页器
this.paginationBox = null;
this.paginationList = null;
if (toType(pagination) === "object") {
let el = pagination.el;
if (el) {
this.paginationBox = container.querySelector(el);
// 创建SPAN
let str = ``;
_each(this.slidesTrue, item => {
str += `<span></span>`;
});
this.paginationBox.innerHTML = str;
this.paginationList = this.paginationBox.querySelectorAll('span');
}
}
// 左右切换
this.arrowPrev = null;
this.arrowNext = null;
if (toType(navigation) === "object") {
navigation.prevEl ? this.arrowPrev = container.querySelector(navigation.prevEl) : null;
navigation.nextEl ? this.arrowNext = container.querySelector(navigation.nextEl) : null;
}
// 控制元素的样式(包含初始展示哪一个)
this.changeWidth = parseFloat(getComputedStyle(container).width);
this.activeIndex = this.activeIndex < 0 ? 0 : (this.activeIndex > this.slides.length - 1 ? this.slides.length - 1 : this.activeIndex);
this.wrapper.style.width = `${this.changeWidth*this.slides.length}px`;
this.wrapper.style.transition = `left ${this.speed}ms`;
this.wrapper.style.left = `${-this.activeIndex*this.changeWidth}px`;
_each(this.slides, item => {
item.style.width = `${this.changeWidth}px`;
});
}
复制代码
2、实现轮播图切换函数
// 实现轮播图切换
change(now = false) {
//now 传值是立即切换,不传是默认的有动画切换
let {
wrapper,
speed,
activeIndex,
changeWidth,
on
} = this;
let isO = toType(on) === "object" ? true : false,
transitionStart = isO ? on.transitionStart : null,
transitionEnd = isO ? on.transitionEnd : null;
// 切换之前触发的钩子函数
!now && transitionStart ? transitionStart.call(this, this) : null;
// 如果传了now 立即切换,则不需要有动画
wrapper.style.transitionDuration = `${now?0:speed}ms`;
wrapper.style.left = `${-activeIndex*changeWidth}px`;
if (now) {
// 如果立即切换我需要让上面先渲染一次,利用读写分离;
wrapper.offsetWidth;
}
// 切换之后触发的钩子函数
let fn = () => {
!now && transitionEnd ? transitionEnd.call(this, this) : null;
// 每一次都会重新监听,所以监听完需要把上一次监听的移除掉
wrapper.removeEventListener('transitionend', fn);
};
wrapper.addEventListener('transitionend', fn);
}
复制代码
3、自动轮播函数
// 自动轮播
autoMove() {
if (this.activeIndex === this.slides.length - 1) {
this.activeIndex = 0;
this.change(true);
}
this.activeIndex++;
this.change();
}
复制代码
4、焦点对齐函数
// 实现焦点对齐
autoFocus() {
let {
paginationList,
activeIndex,
slides
} = this;
if (!paginationList) return;
activeIndex === slides.length - 1 ? activeIndex = 0 : null;
_each(paginationList, (item, index) => {
if (index === activeIndex) {
item.className = 'active';
return;
}
item.className = '';
});
}
复制代码
(五)、第五步——公共方法执行顺序及条件(重点)
- 现在实现一个轮播图功能的方法,我们都已经封装完成,但方法是死的,只有执行才有价值,
- 这个时候我们就要考虑到,我们虽然创建了这些方法,但是用户传递的参数中可能并不需要用到这些,所以我们还需要判断用户是否需要,如果用户需要,那我们的执行顺序又是什么;
init() {
// THIS:当前类的实例
// 入口,在这里控制代码执行的逻辑顺序
// 先基于它获取元素后,才可以获取实例上的信息
this.computed();
let {
autoplay,
autoMove,
container,
pagination,
paginationList,
arrowNext,
arrowPrev
} = this;
// 控制是否自动切换
if (autoplay) {
this.autoTimer = setInterval(autoMove.bind(this), autoplay);
// 箭头函数中的THIS是上下文中的,也就是实例
container.onmouseenter = () => clearInterval(this.autoTimer);
container.onmouseleave = () => this.autoTimer = setInterval(autoMove.bind(this), autoplay);
}
// 控制焦点切换
if (toType(pagination) === "object" && pagination.clickable === true && paginationList) {
_each(paginationList, (item, index) => {
item.onclick = () => {
let {
activeIndex,
slides
} = this;
if ((index === activeIndex) || (index === 0 && activeIndex === slides.length - 1)) return;
this.activeIndex = index;
this.change();
};
});
}
// 控制左右按钮切换
arrowNext ? arrowNext.onclick = autoMove.bind(this) : null;
arrowPrev ? arrowPrev.onclick = () => {
if (this.activeIndex === 0) {
this.activeIndex = this.slides.length - 1;
this.change(true);
}
this.activeIndex--;
this.change();
} : null;
// 初始化完成,触发INIT回调函数
if (toType(this.on) === "object" && isFunction(this.on.init)) {
// 把回调函数执行,让方法中的THIS是实例,并且传递的第一个参数也是实例
this.on.init.call(this, this);
}
}
复制代码
(六)、第六步——整合优化(重点)
上面JS代码汇总整理
使用方式JS倒入即可;仓库地址
- 这里笔者没有放地址,同样需要您创建一个
banner-plugin.js
的JS文件,把代码直接粘贴进去即可;
- 这里笔者没有放地址,同样需要您创建一个
(function () {
class Banner {
constructor(container, options) {
// 把传递进来的信息都挂载到当前类的实例上
// 1.信息都作为他的私有属性(这样每一个实例之间互不影响)
// 2.挂载到实例上,以后在当前类的其它方法中,只要保证THIS是实例,都可以基于THIS.XXX获取和操作
_each(options, (item, key) => {
this[key] = item;
});
this.container = container;
this.activeIndex = this.initialSlide;
this.init();
}
init() {
// THIS:当前类的实例
// 入口,在这里控制代码执行的逻辑顺序
// 先基于它获取元素后,才可以获取实例上的信息
this.computed();
let {
autoplay,
autoMove,
container,
pagination,
paginationList,
arrowNext,
arrowPrev
} = this;
// 控制是否自动切换
if (autoplay) {
this.autoTimer = setInterval(autoMove.bind(this), autoplay);
// 箭头函数中的THIS是上下文中的,也就是实例
container.onmouseenter = () => clearInterval(this.autoTimer);
container.onmouseleave = () => this.autoTimer = setInterval(autoMove.bind(this), autoplay);
}
// 控制焦点切换
if (toType(pagination) === "object" && pagination.clickable === true && paginationList) {
_each(paginationList, (item, index) => {
item.onclick = () => {
let {
activeIndex,
slides
} = this;
if ((index === activeIndex) || (index === 0 && activeIndex === slides.length - 1)) return;
this.activeIndex = index;
this.change();
};
});
}
// 控制左右按钮切换
arrowNext ? arrowNext.onclick = autoMove.bind(this) : null;
arrowPrev ? arrowPrev.onclick = () => {
if (this.activeIndex === 0) {
this.activeIndex = this.slides.length - 1;
this.change(true);
}
this.activeIndex--;
this.change();
} : null;
// 初始化完成,触发INIT回调函数
if (toType(this.on) === "object" && isFunction(this.on.init)) {
// 把回调函数执行,让方法中的THIS是实例,并且传递的第一个参数也是实例
this.on.init.call(this, this);
}
}
// 计算结构和样式
computed() {
let {
container,
pagination,
navigation
} = this;
// 轮播图
this.wrapper = container.querySelector('.xiaozhima-wrapper');
this.slidesTrue = container.querySelectorAll('.xiaozhima-slide');
// 克隆第一张到容器的末尾
this.wrapper.appendChild(this.slidesTrue[0].cloneNode(true));
this.slides = container.querySelectorAll('.xiaozhima-slide');
// 分页器
this.paginationBox = null;
this.paginationList = null;
if (toType(pagination) === "object") {
let el = pagination.el;
if (el) {
this.paginationBox = container.querySelector(el);
// 创建SPAN
let str = ``;
_each(this.slidesTrue, item => {
str += `<span></span>`;
});
this.paginationBox.innerHTML = str;
this.paginationList = this.paginationBox.querySelectorAll('span');
}
}
// 左右切换
this.arrowPrev = null;
this.arrowNext = null;
if (toType(navigation) === "object") {
navigation.prevEl ? this.arrowPrev = container.querySelector(navigation.prevEl) : null;
navigation.nextEl ? this.arrowNext = container.querySelector(navigation.nextEl) : null;
}
// 控制元素的样式(包含初始展示哪一个)
this.changeWidth = parseFloat(getComputedStyle(container).width);
this.activeIndex = this.activeIndex < 0 ? 0 : (this.activeIndex > this.slides.length - 1 ? this.slides.length - 1 : this.activeIndex);
this.wrapper.style.width = `${this.changeWidth * this.slides.length}px`;
this.wrapper.style.transition = `left ${this.speed}ms`;
this.wrapper.style.left = `${-this.activeIndex * this.changeWidth}px`;
_each(this.slides, item => {
item.style.width = `${this.changeWidth}px`;
});
this.autoFocus();
}
// 自动轮播
autoMove() {
if (this.activeIndex === this.slides.length - 1) {
this.activeIndex = 0;
this.change(true);
}
this.activeIndex++;
this.change();
}
// 实现轮播图切换
change(now = false) {
//now 传值是立即切换,不传是有动画切换
let {
wrapper,
speed,
activeIndex,
changeWidth,
on
} = this;
let isO = toType(on) === "object" ? true : false,
transitionStart = isO ? on.transitionStart : null,
transitionEnd = isO ? on.transitionEnd : null;
// 切换之前触发的钩子函数
!now && transitionStart ? transitionStart.call(this, this) : null;
// 如果传了now 立即切换,则不需要有动画
wrapper.style.transitionDuration = `${now ? 0 : speed}ms`;
wrapper.style.left = `${-activeIndex * changeWidth}px`;
if (now) {
// 如果立即切换我需要让上面先渲染一次,利用读写分离;
wrapper.offsetWidth;
} else {
this.autoFocus();
}
// 切换之后触发的钩子函数
let fn = () => {
!now && transitionEnd ? transitionEnd.call(this, this) : null;
// 每一次都会重新监听,所以监听完需要把上一次监听的移除掉
wrapper.removeEventListener('transitionend', fn);
};
wrapper.addEventListener('transitionend', fn);
}
// 实现焦点对齐
autoFocus() {
let {
paginationList,
activeIndex,
slides
} = this;
if (!paginationList) return;
activeIndex === slides.length - 1 ? activeIndex = 0 : null;
_each(paginationList, (item, index) => {
if (index === activeIndex) {
item.className = 'active';
return;
}
item.className = '';
});
}
}
function bannerPlugin(container, options = {}) {
let defaultParams = {
initialSlide: 0,
autoplay: 3000,
speed: 300,
pagination: {
el: '.xiaozhima-pagination',
clickable: true
},
navigation: {
nextEl: '.xiaozhima-arrow-next',
prevEl: '.xiaozhima-arrow-prev'
},
on: {
init() { },
transitionStart() { },
transitionEnd() { }
}
};
options = _assignDeep(defaultParams, options);
typeof container === "string" ? container = document.querySelector(container) : null;
if (!container || container.nodeType !== 1) {
throw new TypeError('container must be an element!');
}
return new Banner(container, options);
}
window.bannerPlugin = bannerPlugin;
})();
复制代码
五、使用须知
必须设置的样式
- 大容器的宽高
- 您自己的图片
JS的使用语法
- bannerPlugin(大容器,用户需求的参数信息)
- container:容器标签
- options:需要配置项
- 参数信息详见 参数信息说明
笔者也在前端这条路上不断的学习和尝试,文中内容有不对的地方,还请赐教,不胜感激🙏;
笔者想在掘金不断成长,现阶段目标升到3级,还望大家成全😄,不胜感激;