如何用面向对象的思维去封装一个小型轮播图插件

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:如果这篇文章对您有一些启发,希望您点一下推荐哦!

posted @ 2018-08-08 21:36 余大彬 阅读(...) 评论(...) 编辑 收藏