原生JS面向對象思想封裝輪播圖組件
在前端頁面開發過程中,頁面中的輪播圖特效很常見,因此我就想封裝一個自己的原生JS的輪播圖組件。有了這個需求就開始着手准備了,代碼當然是以簡潔為目標,輪播圖的各個功能實現都分別分為不同的模塊。目前我封裝的這個版本還不適配移動端,只適配PC端。
主要的功能有:自動輪播,點擊某一張圖片對應的小圓點就跳轉到指定圖片,有前后切換按鈕。使用的時候只需要傳入圖片的路徑以及每張圖片分別所對應的跳轉路徑還有目標盒子ID就可以了,還可以自定義每張圖輪播的延時,不過延時參數不是必須的參數,可以不指定延時,使用默認延時。
在這里我使用的是替換原型對象實現繼承的方式來實現代碼,下面我詳細介紹一下我所分的各個模塊:首先是初始化操作,接着是頁面布局模塊,事件綁定模塊,自動輪播與樣式綁定模塊,還有就是一些使用到的封裝的功能函數,最后有一個AMD規范支持設置。話不多說,直接上代碼:
/**
* 輪播圖組件
* @param opts 參數列表: 圖片參數 圖片跳轉路徑 輪播圖延時
* @param targetId 目標盒子ID 必需
* @constructor
*/
//封裝要實現每個函數功能的單一性
//傳遞一個參數w,減少跨作用域查找
(function (w) {
//不變的內容放在構造函數外面
//模板字符串
var template = '<div class="w-slider" id="js_slider">' +
'<div class="slider">' +
'<div class="slider-main" id="slider_main_block">' +
'</div>' +
'</div>' +
'<div class="slider-ctrl" id="slider_ctrl">' +
'<span class="slider-ctrl-prev"><</span>' +
'<span class="slider-ctrl-next">></span>' +
'</div>' +
'</div>';
var imgStr = "<div class='slider-main-img'><a href='{{href}}'><img src='{{src}}'/></a></div>";
//默認參數
var defaultOpts = {
time: 2000
};
/**
* 添加事件
* @param target 給誰添加
* @param type 添加的事件類型
* @param handler 事件處理函數
*/
var addEvents = (function () {
//能力檢測
if (window.addEventListener) {
return function(target, type, handler) {
target.addEventListener(type,handler,false);
};
} else if (window.attachEvent) {
return function(target, type, handler) {
target.attachEvent("on" + type, handler);
};
}
})();
/**
* 獲取樣式的屬性值
* @param obj
* @param attr
* @returns {*} 返回值帶有單位
*/
var getStyle = function (obj, attr) {
if (obj && obj.currentStyle) {
return obj.currentStyle[attr];
} else {
return getComputedStyle(obj, null)[attr];
}
};
function Carousel(targetId, opts) {
if (!targetId) throw new Error("請傳入目標盒子");
this.targetId = document.getElementById(targetId);
this.str = template;
this.targetId.innerHTML = this.str;
this.bigBox = document.getElementById("js_slider");
this.parentBox = document.getElementById("slider_main_block");
this.ctrlBox = document.getElementById("slider_ctrl");
opts = opts || defaultOpts;
//混入繼承 判斷傳入的opts是否有默認參數中的值,如果默認參數值不存在opts中
// 就把默認參數加進opts中,這樣就不會把默認參數修改了
for (var k in defaultOpts) {
if(!opts[k]) {
opts[k] = defaultOpts[k];
}
}
for (var k in opts) {
this[k] = opts[k];
}
this.timer = null; //總定時器
this.iNow = 0; //控制播放張數,是哪個圖片動
this.init();
}
Carousel.prototype = {
constructor: Carousel,
//初始化頁面,事件綁定
init: function () {
this._createNode();
this._addEvents();
this.timer = setInterval(this._autoPlay(), this.time);
},
//節點生成,頁面布局
_createNode: function () {
var _this = this;
var newimgStr = "";
for (var i = 0; i < this.imgData.length; i++) {
//生成輪播圖節點
newimgStr += imgStr.replace("{{href}}",this.imgData[i].href).replace("{{src}}",this.imgData[i].src);
this.parentBox.innerHTML = newimgStr;
this.imgs = this.parentBox.children; //輪播圖圖片
var img = this.imgs[0].getElementsByTagName("img")[0];
//生成控制按鈕節點
var span = document.createElement("span");
span.setAttribute("class", "slider-ctrl-con");
span.innerHTML = this.imgData.length - i; //設置span的文本內容方便后面使用
this.ctrlBox.insertBefore(span, this.ctrlBox.children[1]);
img.onload = function () {
//圖片加載完全之后設置大盒子寬高
_this.scrollWidth = img.offsetWidth; //大盒子寬度
_this.scrollHeight = img.offsetHeight; //大盒子高度
_this.bigBox.style.width = _this.scrollWidth + "px";
_this.ctrlBox.style.width = _this.scrollWidth + "px";
_this.bigBox.style.height = _this.scrollHeight + "px";
_this.ctrlBox.style.height = _this.scrollHeight + "px";
//設置行高為圖片高度
_this.prev = _this._getFirstElement(_this.ctrlBox);
_this.next = _this._getLastElement(_this.ctrlBox);
_this.prev.style.lineHeight = _this.scrollHeight + 100 + "px";
_this.next.style.lineHeight = _this.scrollHeight + 100 + "px";
//第一張圖片在原位置,其余全部移動到盒子右側
for (var j = 1; j < _this.imgs.length; j++) {
_this.imgs[j].style.left = _this.scrollWidth + "px";
}
};
}
//第一個高亮
this.spans = this.ctrlBox.children;
this.spans[1].setAttribute("class", "slider-ctrl-con current");
},
//事件綁定
_addEvents: function () {
var _this = this;
this.over = true; //節流閥
//監聽單擊事件,遍歷左右箭頭和下方小方塊
for (var k in this.spans) { //this.spans中的屬性中包含0-5數字和一個length屬性,要排除length屬性
if(k.length === 1 ) {
addEvents(this.spans[k], "click", function () {
//點擊的是左側按鈕
if (this.className === "slider-ctrl-prev") {
if (_this.over) {
_this.over = false;
_this._animateEffect(_this.imgs[_this.iNow], {left: _this.scrollWidth}, function () {
_this.over = true;
}); //當前圖片右移
--_this.iNow < 0 ? _this.iNow = _this.imgs.length - 1 : _this.iNow;
_this.imgs[_this.iNow].style.left = -_this.scrollWidth + "px"; //后一張圖片迅速移到最左邊
_this._animateEffect(_this.imgs[_this.iNow], {left: 0}, function () {
_this.over = true;
}); //后一張圖片接着右移動
_this._setSquare();
}
}
//6.點擊右側按鈕,當前圖片左移,后一張圖片接着后面左移動
else if (this.className === "slider-ctrl-next") {
_this._autoPlay()();
}
//7.點擊下方span開始
else {
var that = this.innerHTML - 1; //先保存點擊的span的索引
if (that > _this.iNow) { //當點擊的span的位置是在當前span位置的右邊時,類似於點擊了右側按鈕
_this._animateEffect(_this.imgs[_this.iNow], {left: -_this.scrollWidth}); //當前圖片左移
_this.imgs[that].style.left = _this.scrollWidth + "px"; //圖片索引為that的迅速移動到最右側,再接着左移
} else if (that < _this.iNow) { //當點擊的span的位置是在當前span位置的左邊時,類似於點擊了左側按鈕
_this._animateEffect(_this.imgs[_this.iNow], {left: _this.scrollWidth}); //當前圖片右移
_this.imgs[that].style.left = -_this.scrollWidth + "px"; //圖片索引為that的迅速移動到最左側,再接着右移
}
_this.iNow = that; //點擊的是當前span
_this._animateEffect(_this.imgs[_this.iNow], {left: 0});
_this._setSquare();
}
});
}
}
//監聽鼠標移入移出事件
addEvents(_this.bigBox, "mouseover", function () {
clearInterval(_this.timer);
_this.prev.style.display = "block";
_this.next.style.display = "block";
});
addEvents(_this.bigBox, "mouseout", function () {
clearInterval(_this.timer); //要使用定時器先清除
//把當前this作為參數傳遞到定時器中的函數中
_this.timer = setInterval(_this._autoPlay(), _this.time);
_this.prev.style.display = "none";
_this.next.style.display = "none";
});
},
//同步當前小方塊樣式
_setSquare: function () {
for (var i = 1; i < this.spans.length - 1; i++) {
this.spans[i].setAttribute("class", "slider-ctrl-con");
}
//iNow是從0開始,而spans[0]是左箭頭,因此iNow+1
this.spans[this.iNow + 1].setAttribute("class", "slider-ctrl-con current");
},
//自動輪播,返回一個匿名函數,傳遞實例的this到定時器中,默認定時器中this是指向window
_autoPlay: function () {
var _this = this;
return function () {
if (_this.over) {
_this.over = false;
_this._animateEffect(_this.imgs[_this.iNow], {left: -_this.scrollWidth}, function () {
_this.over = true;
}); //當前圖片左移
++_this.iNow > _this.imgs.length - 1 ? _this.iNow = 0 : _this.iNow;
_this.imgs[_this.iNow].style.left = _this.scrollWidth + "px"; //前一張圖片迅速移到最右邊
_this._animateEffect(_this.imgs[_this.iNow], {left: 0}, function () {
_this.over = true;
}); //接着往左移
_this._setSquare();
}
}
},
/**
* 獲取第一個節點
* @param element
* @returns {*}
*/
_getFirstElement: function (element) {
if (element.firstElementChild) {
return element.firstElementChild;
} else {
var ele = element.firstChild;
while (ele && ele.nodeType !== 1) {
ele = ele.nextSibling;
}
return ele;
}
},
/**
* 獲取最后一個節點
* @param element
* @returns {*}
*/
_getLastElement: function (element) {
if (element.lastElementChild) {
return element.lastElementChild;
} else {
var ele = element.lastChild;
while (ele && ele.nodeType !== 1) {
ele = ele.previousSibling;
}
return ele;
}
},
/**
* 動畫函數
* @param obj 運動的對象
* @param json 改變的屬性
* @param fn 回調函數
*/
_animateEffect: function (obj, json, fn) {
var _this = this;
clearInterval(obj.timer);
obj.timer = setInterval(function () {
var flag = true; //先假設所有屬性都已到達目標位置
for (var k in json) {
if (k == "opacity") { //判斷透明度情況
var leader = json[k] * 100; //透明度變化不設置漸變
var target = json[k] * 100; //目標位置
if ("opacity" in obj.style) {
obj.style[k] = leader / 100;
} else {
obj.style.filter = "alpha(opacity = " + target + ")"; //兼容IE6
}
} else if (k == "zIndex") { //判斷層級情況
leader = parseInt(getStyle(obj, k)) || 0; //層級變化也不設置漸變
target = json[k];
obj.style[k] = target;
} else {
leader = parseInt(getStyle(obj, k)) || 0;
target = json[k];
var step = target > leader ? Math.ceil((target - leader) / 10) : Math.floor((target - leader) / 10); //緩動公式
leader += step;
obj.style[k] = leader + "px";
}
if (leader != target) { //只要有一個屬性沒到達目標位置
flag = false;
}
}
if (flag) { //當所有屬性都到達目標位置時清除定時器
clearInterval(obj.timer);
if (fn) { //如果有回調函數執行回調函數
fn();
}
}
}, 15);
}
};
//支持AMD模塊化
if(typeof define !== "undefined" && typeof define === "function") {
define("Carousel",[],function(){
return Carousel;
});
}else {
w.Carousel = Carousel;
}
}(window));
以上就是js的實現,不過只有這個還是不夠,要配合指定的樣式來使用才行,下面是具體的HTML代碼和CSS樣式:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.w-slider {
margin: 100px auto;
overflow: hidden;
position: relative;
}
.slider {
width: 100%;
height: 100%;
}
.slider-main {
width: 200%;
height: 100%;
}
.slider-main-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.slider-main-img a{
display: inline-block;
width: 100%;
}
.slider-ctrl {
position: relative;
margin-top: -30px;
bottom: 0;
left: 50%;
width: 100%;
transform: translate(-50%);
text-align: center;
padding-top: 5px;
}
.slider-ctrl-con {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: deepskyblue;
display: inline-block;
margin: 0 5px;
cursor: pointer;
text-indent: -40em;
overflow: hidden;
}
.current {
background-color: #46ff19;
}
.slider-ctrl-prev,
.slider-ctrl-next {
font-size: 60px;
font-weight: 800;
position: absolute;
top: -105%;
width: 15%;
text-align: center;
background-color: #ccc;
color: #ff3126;
opacity: 0.4;
cursor: pointer;
display: none;
}
.slider-ctrl-prev {
left: 0;
}
.slider-ctrl-next {
right: 0;
}
</style>
<script src="js/Carousel.js"></script>
</head>
<body>
<div id="carousel"></div>
<script>
window.onload = function() {
//使用姿勢:傳入目標盒子ID和圖片配置信息即可
//配置信息
var opts = {
//圖片信息(必需)
imgData: [
{href: "#",src: "images/a.jpg"},
{href: "#",src: "images/b.jpg"},
{href: "#",src: "images/c.jpg"},
{href: "#",src: "images/d.jpg"}
],
time: 2000 //輪播圖延時(可選)
};
//實例化
new Carousel("carousel",opts);
};
</script>
</body>
</html>
通過以上兩個文件,在使用的時候你就可以直接new 實例化出一個輪播圖到頁面中指定位置,是不是很方便?不過還有很多需要優化的地方,比如我封裝的這個運動函數就不是很完美,這個輪播圖組件還是有點小BUG的,就是在左右切換圖片的時候左側邊緣有時會出現一點白條,主要原因估計就是運動函數的問題。
使用原生封裝完成之后給我的感覺就是要考慮的兼容性問題很多,在封裝的過程中好懷念jQuery啊,使用起來簡單粗暴,不用考慮兼容性的問題。不過通過考慮兼容性問題的過程中我還是收獲了很多,比如說上文的一個添加事件的函數addEvents,一開始我的方式是這樣的:
/**
* 添加事件
* @param target 給誰添加
* @param type 添加的事件類型
* @param handler 事件處理函數
*/
addEvent: function (target, type, handler) {
//能力檢測
if (target && target.addEventListener) {
return target.addEventListener(type, handler, false);
}else if(target && target.attachEvent){
return target.attachEvent("on" + type, handler);
}else {
return target["on" + type] = handler;
}
}
這里存在一個問題,就是我在頁面中每次給一個元素注冊事件的時候,都會進行三次判斷過程。每注冊一次事件就會判斷三次,這樣就浪費了很多內存,考慮到這個問題,所以我對這個事件注冊函數進行了一點改進,如下:
/**
* 添加事件
* @param target 給誰添加
* @param type 添加的事件類型
* @param handler 事件處理函數
*/
var addEvents = (function () {
//能力檢測
if (window.addEventListener) {
return function(target, type, handler) {
target.addEventListener(type,handler,false);
};
} else if (window.attachEvent) {
return function(target, type, handler) {
target.attachEvent("on" + type, handler);
};
}
})();
就是把判斷的過程包裹在一個立即執行函數中,當頁面加載到這里會自動根據瀏覽器的類型來判斷當前瀏覽器是支持哪種事件注冊方式,將判斷之后的結果也就是返回一個匿名函數賦值在addEvents這一個變量中。之后每次調用這個注冊事件的函數時就不用再重新進行判斷了,從而解決了這個問題,節約了內存。
這里還有一個小細節,就是我設置了一個默認參數是輪播圖延時的,當用戶有傳這個參數就是使用用戶傳的參數,否則就使用默認參數,在把配置信息opts附加到構造函數的this中時還要考慮默認參數的問題。我是先判斷傳入的opts中的屬性是否有默認參數time屬性,如果opts中沒有默認參數time屬性,就把默認參數time加進opts中,再把配置信息opts附加到構造函數的this中,這樣就不會把默認參數修改了,從而保證了默認參數的一致性,每次實例化默認參數都是相同的。
一個簡單的小組件,請高手指點指點。
