一、插件封装的思想
基于面向对象的方式来处理
- 调取一次插件相当于创建插件的一个实例
- 这样私有的可以设置,公有的方法也可以设置
我们要保证几个事情
- 灵活且功能强大(适配更多的应用场景)
- 容错性和可扩展性要强
- 追求极致的性能和优秀的代码管理方式
- 开源精神
细节知识点
- 我们封装公共方法的时候,如果需要传递的参数过多(超过两个就可以理解为多了),则不要定义形参,让用户依次传递,这样会受到顺序、传或者不传等因素的影响,管理起来很复杂。我们可以把需要传递的值统一放到一个对象中(一般都是 options),这样我们传递的信息可传可不传,顺序也随便,最后把传递的信息覆盖默认的信息即可,管理方便。也方便进行二次扩展
- 我们把后期需要用到的信息都挂载(给谁谁谁设置了啥啥啥)到当前的实例上,这样后面不管在那个方法中用这些信息,只要能获取到实例,直接通过实例获取既可
- 本插件中需要使用的工具类方法,我们尽量都放到类的私有属性上(普通对象)
二、枚举属性
可枚举属性、不可枚举属性
Object.prototype.AAA = 'zhufeng'
let obj = {
name:'zhufeng',
year:10
// __protp__:Object.prototype
};
for (let key in obj) {
if(!obj.hasOwnProperty(key)) break;
// 防止别人在原型上扩展的方法,是我们不需要的
console.log(key);
}
在 for in 遍历循环的时候,可以被迭代到的属性是可枚举属性,反之是不可枚举的属性。 可枚举的:一般是自己设置的私有属性和方法或者自己设置的共有属性和方法 不可枚举的:js 中基本包装类型的原型属性是不可枚举的,如 Object, Array, Number 等
三、向jQuery扩展插件
JQUERY:extend 向 JQ 内部扩展方法
// 向 JQ 的原型上扩展方法[写插件]
$.fn.extend({ xxx:function () {} })
$('.box').xxx();
// 向 JQ 对象中增加私有的属性方法[完善类库,提供更多工具类方法]
$.extend({ xxx:function () {} })
$.xxx();
代码部分
~ function ($) {
if (typeof $ === 'undefined') {
// throw new Error() 抛出浏览器异常信息,此时下面代码不在执行
throw new Error('当前插件必须依托JQUERY才可以实现~~');
}
// THROTTLE:函数节流
function throttle(func, wait) {
let timer = null,
result = null,
previous = 0;
return function anonymous(...args) {
let context = this,
now = new Date,
spanTime = wait - (now - previous);
if (spanTime <= 0) {
result = func.call(context, ...args);
clearTimeout(timer);
timer = null;
previous = now;
} else if (!timer) {
timer = setTimeout(() => {
result = func.call(context, ...args);
timer = null;
previous = new Date;
}, spanTime);
}
return result;
}
}
// BANNER-PLUGIN:只封装和轮播图相关的功能(自动轮播、焦点触发、左右按钮)
function bannerPlugin() {
// this:要实现轮播图的容器(原生 JS 对象)
let $this = $(this),
$wrapper = $this.find('.wrapper'),
$pagination = $this.find('.pagination'),
$buttonPrev = $this.find('.button-prev'),
$buttonNext = $this.find('.button-next'),
$slideList = $wrapper.find('.slide'),
$paginationList = $pagination.find('span');
let autoTimer = null,
interval = 1000,
speed = 300,
activeIndex = 0,
count = $slideList.length;
let change = function () {
let $active = $slideList.eq(activeIndex),
$siblings = $active.siblings();
$active.css('transition', `opacity ${speed}ms`);
$siblings.css('transition', `opacity 0ms`);
$active.css('z-index', 1);
$siblings.css('z-index', 0);
$active.css('opacity', 1).on('transitionend', function () {
$siblings.css('opacity', 0);
});
// 焦点对齐
$paginationList.each((index, item) => {
let $item = $(item);
if (index === activeIndex) {
$item.addClass('active');
return;
}
$item.removeClass('active');
});
};
let autoMove = function () {
activeIndex++;
activeIndex >= count ? activeIndex = 0 : null;
change();
};
let handlePagination = function () {
$paginationList.mouseover(throttle(function () {
activeIndex = $(this).index();
change();
}, 500));
};
let handleButton = function () {
$buttonNext.click(throttle(autoMove, 300));
$buttonPrev.click(throttle(function () {
activeIndex--;
activeIndex < 0 ? activeIndex = count - 1 : null;
change();
}, 500));
};
autoTimer = setInterval(autoMove, interval);
handlePagination();
handleButton();
$this.mouseenter(() => clearInterval(autoTimer))
.mouseleave(() => autoTimer = setInterval(autoMove, interval));
}
$.fn.extend({
// bannerPlugin:bannerPlugin
bannerPlugin
});
}(jQuery);
四、插件版轮播图
html部分
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>渐隐渐现版轮播图</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<link rel="stylesheet" href="css/banner.css">
</head>
<body>
<section class="container" id="container1">
<!-- 轮播图容器 -->
<div class="wrapper"></div>
<!-- 分页器容器 -->
<div class="pagination"></div>
</section>
<section class="container" id="container2">
<!-- 轮播图容器 -->
<div class="wrapper"></div>
<!-- 前进后退按钮 -->
<a href="javascript:;" class="button-prev"></a>
<a href="javascript:;" class="button-next"></a>
</section>
<!-- IMPORT JS -->
<!-- <script src="js/jquery.min.js"></script>
<script src="js/banner-plugin-jquery.js"></script>
<script src="js/banner.js"></script> -->
<script src="js/jquery.min.js"></script>
<script src="js/banner-plugin.min.js"></script>
<script src="js/banner.js"></script>
</body>
</html>
css部分
.container {
box-sizing: border-box;
position: relative;
margin: 20px auto;
width: 1226px;
height: 460px;
overflow: hidden;
}
.container .wrapper {
position: relative;
height: 100%;
}
.container .wrapper .slide {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
width: 100%;
height: 100%;
z-index: 0;
opacity: 0;
}
.container .wrapper .slide:nth-child(1) {
z-index: 1;
opacity: 1;
}
.container .wrapper .slide img {
width: 100%;
height: 100%;
}
.container .wrapper .slide span{
display: block;
transform: translateY(-100px);
font-size: 30px;
color: red;
}
.container .pagination {
position: absolute;
right: 20px;
bottom: 20px;
z-index: 999;
font-size: 0;
}
.container .pagination span {
display: inline-block;
margin: 0 4px;
width: 6px;
height: 6px;
background: rgba(0, 0, 0, .4);
border: 2px solid rgba(255, 255, 255, .4);
border-radius: 50%;
cursor: pointer;
}
.container .pagination span.active {
background: rgba(255, 255, 255, .4);
border-color: rgba(0, 0, 0, .4);
}
.container .button-prev,
.container .button-next {
position: absolute;
top: 50%;
margin-top: -35px;
z-index: 999;
width: 40px;
height: 70px;
background: url("../images/icon-slides.png") no-repeat;
}
.container .button-prev {
left: 0;
background-position: -83px 0;
}
.container .button-prev:hover {
background-position: 0 0;
}
.container .button-next {
right: 0;
background-position: -124px 0;
}
.container .button-next:hover {
background-position: -41px 0;
}
清除默认样式
body,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,button,input,textarea,th,td{margin:0;padding:0}body{font-size:12px;font-style:normal;font-family:"\5FAE\8F6F\96C5\9ED1",Helvetica,sans-serif}small{font-size:12px}h1{font-size:18px}h2{font-size:16px}h3{font-size:14px}h4,h5,h6{font-size:100%}ul,ol{list-style:none}a{text-decoration:none;background-color:transparent}a:hover,a:active{outline-width:0;text-decoration:none}table{border-collapse:collapse;border-spacing:0}hr{border:0;height:1px}img{border-style:none}img:not([src]){display:none}svg:not(:root){overflow:hidden}html{-webkit-touch-callout:none;-webkit-text-size-adjust:100%}input,textarea,button,a{-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]),video:not([controls]){display:none;height:0}progress{vertical-align:baseline}mark{background-color:#ff0;color:#000}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}button,input,select,textarea{font-size:100%;outline:0}button,input{overflow:visible}button,select{text-transform:none}textarea{overflow:auto}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.clearfix:after{display:block;height:0;content:"";clear:both}
js部分
~ function () {
/* Banner:渐隐渐现轮播图插件 */
class Banner {
constructor(selector, options = {}) {
// 参数初始化
this.initialParams(options);
// 获取需要操作的容器
if (!selector) throw new ReferenceError('The first selector parameter must be passed~~');
if (typeof selector === "string") {
this.container = document.querySelector(selector);
} else if (selector.nodeType) {
this.container = selector;
}
this.wrapper = this.container.querySelector('.wrapper');
this.slideList = this.wrapper.querySelectorAll('.slide');
this.autoTimer = null;
this.activeIndex = this.initialSlide;
this.count = this.slideList.length;
// 初始展示SLIDE
[].forEach.call(this.slideList, (item, index) => {
if (index === this.initialSlide) {
item.style.zIndex = 1;
item.style.opacity = 1;
return;
}
item.style.zIndex = 0;
item.style.opacity = 0;
});
// 自动轮播处理
if (this.autoplay) {
let anonymous = this.autoMove.bind(this);
this.autoTimer = setInterval(anonymous, this.autoplay);
this.container.addEventListener('mouseenter', () => {
clearInterval(this.autoTimer);
});
this.container.addEventListener('mouseleave', () => {
this.autoTimer = setInterval(anonymous, this.autoplay);
});
}
// 分页器的处理
if (this.pagination && this.pagination.el) {
this.handlePagination();
}
// 前进和后退按钮处理
if (this.navigation) {
this.handleButton();
}
// 钩子函数的处理
// 初始化成功
this.on && this.on.init && this.on.init.call(this, this);
}
/*===Banner.prototype===*/
/*
* initialParams:初始化插件的参数配置信息
*/
initialParams(options) {
// 1.首先设置默认的参数信息
let _default = {
initialSlide: 0,
speed: 300,
autoplay: 3000,
pagination: {
el: '.pagination',
triggerEvent: 'click'
},
navigation: {
nextEl: '.button-next',
prevEl: '.button-prev',
hide: true
},
on: {
init: function (examp) {},
transitionStart: function (examp) {},
transitionEnd: function (examp) {}
}
};
// 2.把传递进来的OPTIONS中的信息替换_DEFAULT中的信息
for (let key in options) {
if (!options.hasOwnProperty(key)) break;
if (/^(pagination|navigation|on)$/i.test(key)) continue;
_default[key] = options[key];
}
// pagination
let pagination = options.pagination;
if (pagination !== null) {
pagination = pagination || {};
for (let key in pagination) {
if (!pagination.hasOwnProperty(key)) break;
_default['pagination'][key] = pagination[key];
}
} else {
_default['pagination'] = null;
}
// navigation
let navigation = options.navigation;
if (navigation !== null) {
navigation = navigation || {};
for (let key in navigation) {
if (!navigation.hasOwnProperty(key)) break;
_default['navigation'][key] = navigation[key];
}
} else {
_default['navigation'] = null;
}
// on
let _on = options.on;
if (_on !== null) {
_on = _on || {};
for (let key in _on) {
if (!_on.hasOwnProperty(key)) break;
_default['on'][key] = _on[key];
}
} else {
_default['on'] = null;
}
// 3.把处理好的信息挂载到实例上
for (let key in _default) {
if (!_default.hasOwnProperty(key)) break;
this[key] = _default[key];
}
}
/* 实现轮播图切换 */
change() {
[].forEach.call(this.slideList, (item, index) => {
if (index === this.activeIndex) {
// 当前要操作的SLIDE
item.style.transition = `opacity ${this.speed}ms`;
item.style.zIndex = 1;
return;
}
// 其余的SLIDE
item.style.transition = `opacity 0ms`;
item.style.zIndex = 0;
});
// 开始动画
// 动画开始前的钩子函数
this.on && this.on.transitionStart && this.on.transitionStart.call(this, this);
let active = this.slideList[this.activeIndex];
active.style.opacity = 1;
active.addEventListener('transitionend', () => {
// addEventListener:DOM2级事件绑定
[].forEach.call(this.slideList, (item, index) => {
if (index !== this.activeIndex) {
item.style.opacity = 0;
}
});
// 动画结束后的钩子函数
this.on && this.on.transitionEnd && this.on.transitionEnd.call(this, this);
});
// 焦点对齐
if (this.paginationList) {
[].forEach.call(this.paginationList, (item, index) => {
if (index === this.activeIndex) {
item.className = "active";
return;
}
item.className = "";
});
}
}
/* 自动轮播 */
autoMove() {
this.activeIndex++;
this.activeIndex >= this.count ? this.activeIndex = 0 : null;
this.change();
}
/* 分页器处理 */
handlePagination() {
// 获取分页器盒子,动态创建内容
this.paginationBox = this.container.querySelector(this.pagination.el);
let str = ``;
for (let i = 0; i < this.count; i++) {
str += `<span class='${i===this.activeIndex?'active':''}'></span>`;
}
this.paginationBox.innerHTML = str;
this.paginationList = this.paginationBox.querySelectorAll('span');
// 是否焦点触发切换
if (this.pagination.triggerEvent) {
[].forEach.call(this.paginationList, (item, index) => {
item.addEventListener(this.pagination.triggerEvent, Banner.throttle(() => {
this.activeIndex = index;
this.change();
}, 500));
});
}
}
/* 前进后退按钮 */
handleButton() {
this.prevEl = this.container.querySelector(this.navigation.prevEl);
this.prevEl.addEventListener('click', Banner.throttle(() => {
this.activeIndex--;
this.activeIndex < 0 ? this.activeIndex = this.count - 1 : null;
this.change();
}, 500));
this.nextEl = this.container.querySelector(this.navigation.nextEl);
this.nextEl.addEventListener('click', Banner.throttle(this.autoMove.bind(this), 500));
// 显示隐藏的处理
if (this.navigation.hide) {
this.prevEl.style.display = 'none';
this.nextEl.style.display = 'none';
this.container.addEventListener('mouseenter', () => {
this.prevEl.style.display = 'block';
this.nextEl.style.display = 'block';
});
this.container.addEventListener('mouseleave', () => {
this.prevEl.style.display = 'none';
this.nextEl.style.display = 'none';
});
}
}
/* 设置私有的方法 */
static throttle(func, wait) {
let timer = null,
result = null,
previous = 0;
return function anonymous(...args) {
let context = this,
now = new Date,
spanTime = wait - (now - previous);
if (spanTime <= 0) {
result = func.call(context, ...args);
clearTimeout(timer);
timer = null;
previous = now;
} else if (!timer) {
timer = setTimeout(() => {
result = func.call(context, ...args);
timer = null;
previous = new Date;
}, spanTime);
}
return result;
}
}
}
window.Banner = Banner;
}();