原生JS面向對象思想封裝輪播圖組件javascript
在前端頁面開發過程當中,頁面中的輪播圖特效很常見,所以我就想封裝一個本身的原生JS的輪播圖組件。有了這個需求就開始着手準備了,代碼固然是以簡潔爲目標,輪播圖的各個功能實現都分別分爲不一樣的模塊。目前我封裝的這個版本還不適配移動端,只適配PC端。html
主要的功能有:自動輪播,點擊某一張圖片對應的小圓點就跳轉到指定圖片,有先後切換按鈕。使用的時候只須要傳入圖片的路徑以及每張圖片分別所對應的跳轉路徑還有目標盒子ID就能夠了,還能夠自定義每張圖輪播的延時,不過延時參數不是必須的參數,能夠不指定延時,使用默認延時。前端
在這裏我使用的是替換原型對象實現繼承的方式來實現代碼,下面我詳細介紹一下我所分的各個模塊:首先是初始化操做,接着是頁面佈局模塊,事件綁定模塊,自動輪播與樣式綁定模塊,還有就是一些使用到的封裝的功能函數,最後有一個AMD規範支持設置。話很少說,直接上代碼:java
/** * 輪播圖組件 * @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樣式:node
<!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的,就是在左右切換圖片的時候左側邊緣有時會出現一點白條,主要緣由估計就是運動函數的問題。json
使用原生封裝完成以後給個人感受就是要考慮的兼容性問題不少,在封裝的過程當中好懷念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; } }
這裏存在一個問題,就是我在頁面中每次給一個元素註冊事件的時候,都會進行三次判斷過程。每註冊一次事件就會判斷三次,這樣就浪費了不少內存,考慮到這個問題,因此我對這個事件註冊函數進行了一點改進,以下:ide
/** * 添加事件 * @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中,這樣就不會把默認參數修改了,從而保證了默認參數的一致性,每次實例化默認參數都是相同的。函數
一個簡單的小組件,請高手指點指點。