如何用物件導向的思維去封裝一個小型輪播圖外掛

餘大彬發表於2018-08-08

1、物件導向與程式導向

既然說到物件導向的思維,那就免不了要對程式導向的程式設計思維與物件導向的思維做一番比較。

筆者有 一段時間天真的認為有一個類,然後new一個物件就是物件導向程式設計了,現在想想還是太naive了呀。

其實物件導向的程式設計思維和麵向過程的程式設計思維重要的不是物件和過程,而是思維。

比如說在生活中我們要做一道蕃茄炒雞蛋,很多人的第一反應就是蕃茄炒雞蛋第一步、第二步、第三步應該怎麼做,每一步放什麼調料。然後在做的過程中找每一步需要的調料。這就是很典型的程式導向的思維方式:怎麼做,需要什麼

而有些人的先想到的是做蕃茄炒雞蛋我需要哪些東西,調料等。然後才考慮的是怎麼做。所以一個物件導向的思維方式是:需要什麼,怎麼做。等一下,“怎麼做”指的不是向過程嘛?這就驗證了前面那句話:重要的不是物件和過程,而是思維。不管是哪種程式設計思維,最終落到敲程式碼的層面,都是程式導向的。

咋一看,好像兩種思維方式並沒有很明顯的優劣勢之分呀。這個筆者也曾經糾結好久,其實對於平時一些非常簡單的需求(就是完成一件事所需要的步驟比較少)兩者好像都差不多,甚至程式導向更甚一籌;但是對於一些複雜的需求的話,物件導向可以讓我們的專案更加易於維護和擴充套件。(還記得物件導向的三大特性嘛:繼承、封裝、多型。好吧,JS的世界沒有多型)

--------------------------------------------------------------- 分割線,下面正式開始。

原始碼獲取連結請點這裡

侃完了物件導向和麵向過程,下面我們就來正式的講一下:如何用物件導向的思維去封裝一個小型輪播圖外掛。(用jQuery實現)

廢話不多說,先看最終實現的效果。

 

2、需求分析

其實在實現一個外掛時,我們想要的實現的需求就是"誰去幹一件什麼事"。那麼在這裡就是讓這個container容器裡面的圖片實現輪播。

既然需求確定了,接下來就是實現的環節。既然是這樣的話,第一件事就是把通過HTML和CSS把靜態頁面搭建起來。

頁面建好了就到了使用JS進行動態互動的時候了。這個時候才是真正的重頭戲。讓我們暫時忘掉這樣一個效果是怎麼實現的。我們先來看一看要實現這樣的效果我們需要幹什麼(即需要什麼)。

  • 首先我們要知道整個輪播圖顯示範圍:即整個輪播圖的寬高。width和height
  • 其次我們需要居中顯示的圖片的寬高,這樣我們才能確定後面的圖片的擺放位置。posterWidth和posterHeight
  • 後面圖片的大小百分比也得根據前面圖片的大小來確定。scale
  • 每一張圖片是頂端對齊、居中對齊還是底端對齊。verticalAlign
  • 輪播圖是否需要自動輪播呀,如果需要,那麼輪播時自動切換的時間是多少。autoTime
  • 當然,我們還得設定圖片的切換速度。speed

3、具體實現

好了,現在大致的需求已經確定了,我們先來定一個預設的需求。

setting = {
    "width": 1000,
    "height": 270,
    "posterWidth": 640,//這裡我們沒有設定居中圖片的高度,因為一般情況下,居中圖片的高度與容器的高度相同
    "scale": 0.9,
    "speed": 500,
    "autoPlay": false,
    "autoTime": 2000,
    "verticalAlign": "middle",
}

既然是物件導向的方式實現,我們首先需要一個Carousel函式物件。這個物件的作用是:讓我們的容器裡的圖片根據我們傳進去的配置實現動畫效果。很顯然,這個物件應該接收兩個引數:container和config。

let Carousel = function(poster,config){//這裡的poster相當於container
    let _this = this;
    this.poster = poster;
    this.config = config;
    //獲取容器內要操作的DOM元素
    this.posterItemMain = poster.find('ul.poster-list');
    this.nextBtn = poster.find('div.next-btn');
    this.prevBtn = poster.find('div.prev-btn');
    this.posterItems = this.posterItemMain.find('.poster-item');
    this.length = this.posterItems.length;
    //第一張幻圖片(居中圖片)
    this.posterFirstItem = this.posterItems.first();
    this.posterLastItem = this.posterItems.last();
    //配置預設引數
    this.setting = {
        "width": 1000,
        "height": 270,
        "posterWidth": 640,
        "scale": 0.9,
        "speed": 500,
        "autoPlay": false,
        "autoTime": 2000,
        "verticalAlign": "middle",
    }
}

獲得要操作的元素之後,我們還得把圖片設定在正確的位置。

let Carousel = function(poster,config){
    this.getSetting();//匹配真實配置引數
    this.setSettingValue();//設定容器和居中圖片寬高
    this.setPosterPos();//設定後面圖片的位置關係
}

Carousel.prototype = {

    setPosterPos: function() {
        let sliceItems = this.posterItems.slice(1);
        let sliceRight = sliceItems.slice(0,(sliceItems.length)/2);
        let sliceLeft = sliceItems.slice((sliceItems.length)/2);
        //設定zindex層級
        let level = Math.floor(this.length / 2);
        //左右間隙寬度
        let gapWidth = (this.setting.width - this.setting.posterWidth) / 2 / level;
        
        let rw = this.setting.posterWidth,
            rh = this.setting.height,
            _this = this,
            firstLeft = (this.setting.width - this.setting.posterWidth) / 2,//第一幀距左端的距離
            fixLeft = firstLeft + rw;//第一幀寬加上距左端距離
        
        //設定右邊幀的位置關係,如高度、寬度等
        sliceRight.each(function(index,item){
            rw = rw * _this.setting.scale;
            rh = rh * _this.setting.scale;
            //console.log(fixLeft );
            //獲取到的每一個元素是DOM物件,先轉換為jQuery物件
            $(item).css({
                width:rw,
                height:rh,
                zIndex: level,
                top: _this.setVerticalAlign(rh),
                left:fixLeft + (index + 1) * gapWidth - rw,
                opacity:0.5,
            });
            level--;
        })

        let lw = sliceRight.last().width(),//最左邊圖片的寬度
            lh = sliceRight.last().height(),//最左邊圖片的高度
            level1 = 0;//在Index層級

        //設定左邊幀的位置關係
        sliceLeft.each(function(index,item){
            //獲取到的每一個元素是DOM物件,先轉換為jQuery物件
            $(item).css({
                width:lw,
                height:lh,
                zIndex: level1,
                top: _this.setVerticalAlign(lh),
                left: index * gapWidth,
                opacity:0.5,
            });
            lw = lw / _this.setting.scale;
            lh = lh / _this.setting.scale;
            level1++;
        })

    },

    setSettingValue: function() {
        this.poster.css({
            width: this.setting.width,
            height: this.setting.height,
        });
        this.posterItemMain.css({
            width: this.setting.width,
            height: this.setting.height,
        });

        //計算上下切換按鈕的寬度
        let w = (this.setting.width - this.setting.posterWidth) / 2;
        this.prevBtn.css({
            width: w,
            height: this.setting.height,
            zIndex: Math.ceil(this.length / 2),
        }) ;
        this.nextBtn.css({
            width: w,
            height: this.setting.height,
            zIndex: Math.ceil(this.length / 2),
        }) 
        //console.log(1);
        //設定第一張幻燈片的位置
        this.posterFirstItem.css({
            left: w,
            height: this.setting.height,
            width: this.setting.posterWidth,
            zIndex: Math.ceil(this.length / 2),
        })
    },

    getSetting: function() {
        this.setting = $.extend(this.setting,this.config);
    },
}

圖片的位置都設定好了以後,接下來就是真正的動態互動的邏輯的設定了。

let Carousel = function(poster,config){
    this.nextBtn.click(function() {//為按鈕繫結事件
         _this.carouseRotate('right');     
    });

    this.prevBtn.click(function() {
         _this.carouseRotate('left');
    });

    if (this.setting.autoPlay){//判斷是否自動輪播
        let _this = this;
        this.autoPlay();
        this.poster.hover(function() {
            window.clearInterval(_this.timer);
        },function(){
            _this.autoPlay();
        })
    }
}

Carousel.prototype = {
    //自動播放
    autoPlay: function() {
        let _this = this;
        this.timer = window.setInterval(function() {
            _this.nextBtn.click()
        },_this.setting.autoTime)
    },
    //輪播
    carouseRotate:function(dir) {
    let that = this;
    let zIndexArr = [];//儲存zIndex的值
    if (dir == "left"){
        let arr = [],
        zIndexArr = [];
    this.posterItems.each(function(item,index) {//遍歷圖片,將圖片的位置等資訊存放在一個陣列裡
        let _this = $(this),
            prev = _this.prev().get(0) ? _this.prev() : that.posterLastItem,
            width = prev.width(),
            height = prev.height(),
            zIndex = prev.css('zIndex'),
            opacity = prev.css('opacity'),
            left = prev.css('left'),
            top = prev.css('top');
        arr.push({
            width: width,
            height: height,
            left: left,
            opacity: opacity,
            top: top,
        })
        zIndexArr.push(zIndex);
    })
    this.posterItems.each(function(index) {
        let _this = $(this);
        _this.css('zIndex',zIndexArr[index]);
    })
    this.posterItems.each(function(index,item) {//實現輪播效果
        let _this = $(this);
        //console.log(this);//each迴圈裡的this指向當前元素
        _this.animate(arr[index],that.setting.speed,function() {
            that.flag = true;
        })
    })
    }else {
       //這裡省略了右邊按鈕的輪播程式碼
       //但整體思路和左邊按鈕的輪播程式碼類似
    }   

},
    setVerticalAlign: function(height) {
        if (this.setting.verticalAlign == 'top') {
            return 0;
        }
        if (this.setting.verticalAlign == 'middle' || this.setting.verticalAlign == undefined) {
            return (this.setting.height - height) / 2;
        }

        if (this.setting.verticalAlign == 'bottom'){
            return (this.setting.height - height);
        }
    },

}

至此,一個物件導向的輪播圖元件已經做好了。

3.1、一個小bug

但是還存在著一個小bug。那就是:當多次快速點選按鈕的時候,輪播圖的切換會變得很亂。

這主要是因為當前一個輪播圖切換還在切換過程中的時候,這時你再點選,輪播圖會在當前位置再次進行切換,從而造成輪播圖的混亂。為此,我們希望當點選後,輪播圖的切換還未完成之前,無論點選多少次按鈕都不再觸發輪播。

這個很像多執行緒中的多個執行緒同時操作一個公共資源的問題,為此,我們可以給需要操作的公共資源(這裡指輪播圖切換函式)新增一個鎖,當點選按鈕時,給該資源上鎖,當輪播圖切換完成後,再給這個資源解鎖。

let Carousel = function(poster,config){
    this.flag = true;//輪播圖鎖標識
    this.nextBtn.click(function() {//為按鈕繫結事件
        if (_this.flag) {
            _this.flag = false;//給輪播圖函式上鎖
            _this.carouseRotate('right');
        }    
    });

    this.prevBtn.click(function() {
        if (_this.flag) {
            _this.flag = false;
            _this.carouseRotate('left');
        }       
    });
}

//做了一點點小改動
carouseRotate:function(dir) {
    let that = this;
    let zIndexArr = [];//儲存zIndex的值
    if (dir == "left"){
        let arr = [],
        zIndexArr = [];
    this.posterItems.each(function(item,index) {
        let _this = $(this),
            prev = _this.prev().get(0) ? _this.prev() : that.posterLastItem,
            width = prev.width(),
            height = prev.height(),
            zIndex = prev.css('zIndex'),
            opacity = prev.css('opacity'),
            left = prev.css('left'),
            top = prev.css('top');
        arr.push({
            width: width,
            height: height,
            left: left,
            opacity: opacity,
            top: top,
        })
        zIndexArr.push(zIndex);
    })
    this.posterItems.each(function(index) {
        let _this = $(this);
        _this.css('zIndex',zIndexArr[index]);
    })
    this.posterItems.each(function(index,item) {
        let _this = $(this);
        _this.animate(arr[index],that.setting.speed,function() {
            that.flag = true;//加了一個回撥函式
        })
    })
    }else {
      //省略右輪播程式碼
    }
},

3.2、註冊為jQuery方法 

下面,我們將其作為一個擴充套件功能註冊為jQuery方法。

$.fn.extend({//註冊為jQuery方法
    carousel: function(config) {
        new Carousel($(this),config)
    }
})

 好了,大功告成。

PS:如果這篇文章對您有一些啟發,希望您點一下推薦哦!

相關文章