2017-04-09
FE
制作图片轮播组件
前些日子自己做了个轮播组件,中间过程感觉挺有趣,便写了下来。
起名 BGS.js (BackGroundSlide.js)
设想:
  1. 调用 BGS.init 初始化后返回 BGS实例
  2. 等待操作 (上一张 下一张等等)

# 架构原理

# 选用 CSS 实现

依我所见,有两种比较可行的方式可以实现图片轮播
  1. 利用 img 标签 ( absolute, left: 0% => 100%, 100% => 0 )
  2. 利用 CSS background 属性 ( backgorund-position-x )
我选择第二种方案作为实施方向。

# 关于 background 属性

  1. background-image 可以接受多个参数(用逗号隔开):
    00.bg {
    01     background-image:
    02         url("img/001.jpg"),
    03         url("img/002.jpg"),
    04         url("img/003.jpg"),
    05         url("img/004.jpg"),
    06         url("img/005.jpg"); 
    07}
  2. background-position-x 也可以接受多个参数,而且跟 background-image 一一对应
  3. transitionbackground 也适用
  4. 利用 $(selector).css(‘background-image’, val); 可以调整 background-image 属性,其他的也是一样

# 期望的食用方法

00BGS.init({
01    imgList: [          // 待轮播图片 
02        'img/001.jpg',
03        'img/002.jpg',
04        'img/003.jpg',
05        'img/004.jpg',
06        'img/005.jpg'
07    ],
08    id: 'MyBGS001',     // 指定轮播 id 
09    selector: '.bgimg-container',   // 指定轮播容器 
10    // when the background changing 
11    change: function(idx){          // 轮播切换的回调 
12        console.log('轮播图切换,新位点:%d', idx); 
13    },
14    autoplay: { // 自动播放, wait 是时间  direction 是方向 
15        wait: 5000, 
16        direction: 'toNext'
17    },
18    // transition 曲线 (timing-fucntion)
19    timingFunction: 'cubic-bezier(0.23, 1, 0.32, 1)',  
20    BGInit: function(){ // 初始化回调
21        console.log('BGS 初始化完成'); 
22        this.setHere(3);  // 从位点为 3 的位置,也就是从 003.jpg 开始播放
23    }
24});
此外 BGS 上还期望有以下方法:
  1. init: 初始化,并传入配置, **配置的变量名在init里是 config 在BGS上是 _config **
  2. toNext: 立即切换到下一张
  3. toPre: 立即切换到上一张
  4. toThere: 立即切换到第 N 张 (带动画效果)
  5. setHere: 立即切换到第 N 张 (不带动画效果,瞬变)
  6. change: 图片切换的时候的回调,回调会接受一个参数 这个参数是新位点
  7. stop: 停止自动播放
  8. start: 开始自动播放,需要传递配置参数
  9. restart: 从 stop 后恢复

# 数据实现

00
01// 数组 
02var position = []; 
03// 当 init 传入图片 ['1.jpg', '2.jpg', '3.jpg', '4.jpg'] 
04// position 会初始化为 [0, 1, 2, 3, 4] 
05// 
06// 也就是说 position-x 应该是: 
07// [0, 1 * 容器宽度, 2 * 容器宽度, 3 * 容器宽度, 4 * 容器宽度] 
08
09// 求下一张应该要怎样: 
10// [-3, -2, -1, 0] => [0, 1, 2, 3]
11// [0, 1, 2, 3] => [-1, 0, 1, 2] 相当于全部减一
12position.nextPosition = function(){
13    var i = 0;
14    if (this[this.length-1] == 0){
15        this.setPosition(0); 
16        return this; 
17    }
18    for (i=0;i<this.length;i++){
19        this[i]--; 
20    }
21
22    return this; 
23}
24
25// 求上一张应该要怎样: 
26// [0, 1, 2, 3] => [-3, -2, -1, 0] 
27// [-1, 0, 1, 2] => [0, 1, 2, 3]
28position.prePosition = function(){
29    var i = 0 ;
30    if (this[0] == 0){
31        console.log('循环'); 
32        this.setPosition(this.length-1);
33        return this; 
34    }
35    for (i=0;i<this.length;i++){
36        this[i]++; 
37    }
38    return this; 
39}
40
41// 设置哪位为 0
42// [-3, -2, -1, 0] => [0, 1, 2, 3]
43position.setPosition = function(id){
44    if (id < 0 || id >= this.length){
45        console.warn('setPosition(id): id越界');
46    } else {
47        this[id] = 0;  // 3 
48        var i = 0;
49        for (i;i<this.length;i++){
50            this[i] = i-id; 
51        }
52    }
53    return this; 
54}
55
56// 将数组格式化 其中 $BGSContent 指的是 $(config.selector)
57// 如果 position 为 [-3, -2, -1, 0] 那么格式化后得到字符串: 
58// "-1500px, -1000px, -500px, 0px" 
59position.parse = function(){
60    var i = 0; 
61    var temp = ''; 
62    for (i=0;i<this.length;i++){
63        temp += (this[i] * $BGSContent.width()) + 'px, '; 
64    }
65    return temp.substring(0, temp.length-2);
66}
67
68// 格式化的时候添加偏移 其中 $BGSContent 指的是 $(config.selector)
69// 如果 position 为 [-3, -2, -1, 0] 调用 position.offset(1) 将得到字符串:
70// "-1499px, -999px, -499px, 1px" 
71position.offset = function(e){
72    var i = 0; 
73    var temp = ''; 
74    for (i=0;i<this.length;i++){
75        temp += (this[i] * $BGSContent.width() + e) + 'px, '; 
76    }
77    return temp.substring(0, temp.length-2);
78}
79
80// 返回 position 哪位是 0 
81// 即返回播放到哪张图片了 
82// 对于 [0, 1, 2, 3], where将返回 0 
83position.where = function(){
84    var i=0;
85    for (i=0;i<this.length;i++){
86        if (this[i] == 0){
87            return i; 
88        }
89    }
90    return -1; 
91}

# 视图渲染实现

设计函数 render 把参数 positionx 渲染到轮播图css上
00function render(positionx){ // position.parse 的结果可以传递进来渲染到css上面
01    $BGSContent.css('background-position-x', positionx); 
02    // 触发回调 
03    // change 是 config.change 
04    change(position.where()); 
05}
init 的时候需要在 config.selector 里面生成 轮播图span :
00var BGSTemplate; 
01
02if (config.id){ // 如果config里面提供了 id 
03    BGSTemplate = '<span id="' + config.id + '"class="BGS-content BGS-transition"></span>';
04    BGSContentSelctor = '#'+config.id;  
05} else { // 如果没有 
06    BGSTemplate = '<span class="BGS-content BGS-transition"></span>';
07    BGSContentSelctor = '.BGS-content'; 
08}
09
10// .... 省略 
11
12$BGSContent = $(BGSContentSelctor);
需要添加 stlye 标签添加类
00var BGSClass =  '<style>' +
01'.BGS-content {' +
02    'background-repeat: no-repeat;' +
03    'width: 100%;' +
04    'height: 100%;' +
05    'background-color: #BBB;' +
06    'display: block;' +
07    'background-position-y: center;' +
08    'cursor: pointer;' + 
09    'background-size: 100%;' + 
10'}' +
11'.BGS-transition {' +
12    'transition: all .8s; ' +
13    'transition-timing-function:'; 
14    // 剩下的这部分将在init那里补全
15    // cubic-bezier(0.23, 1, 0.32, 1)'}</style>';

# 控制器

实现 下一张 上一张 第N张 第N张(不带动画)
00function toNext(){
01    position.nextPosition();
02    render(position.parse());
03}
04
05function toPre(){
06    position.prePosition();
07    render(position.parse());
08}
09
10function toThere(idx){
11    position.setPosition(idx); 
12    render(position.parse());
13}
14
15// 实现切换图片的时候不带动画 
16// 先移除 transition 再调整position 再添加回 transition 
17function setHere(idx){
18    $BGSContent.removeClass('BGS-transition'); 
19    toThere(idx); 
20    setTimeout(function(){
21        $BGSContent.addClass('BGS-transition'); 
22    })
23}
实现自动轮播,暂停,重启轮播
00var _direction;   // 内部变量
01var _autoplay_id; // 内部变量 
02
03// 启动轮播
04function start(todo){ // todo 要么是 toNext 函数 要么是 toPre 函数 
05    _direction = todo; // 暂存 
06    _autoplay_id = setInterval(todo, _config.autoplay.wait);
07    console.info('BGS 定时器启动 (新方向)');
08}
09
10// 暂停 
11function stop(){
12    clearInterval(_autoplay_id); 
13    console.info('BGS 定时器暂停');
14}
15
16// 重启 
17function restart(){
18    _autoplay_id = setInterval(_direction, _config.autoplay.wait);
19    console.info('BGS 定时器重启');
20}
加入鼠标拖放的支持(鼠标拖放 != 屏幕触拖)
00function scrollInit(){
01    var isPress = false; 
02    var scrollStartX = false;   
03    var judgeWidth;
04    var scrollDistance = 0; 
05    $BGSContent.mousedown(function(e){
06        isPress = true; 
07        scrollStartX = e.offsetX; 
08        judgeWidth = $BGSContent.width() / 10;
09        $BGSContent.removeClass('BGS-transition'); 
10    }); 
11    $BGSContent.mouseup(function(){
12        isPress = false; 
13        scrollStartX = false; 
14        $BGSContent.addClass('BGS-transition'); 
15        if (Math.abs(scrollDistance) > judgeWidth) {
16            if (scrollDistance > 0){
17                toNext(); 
18            } else {
19                toPre(); 
20            }
21        } else {
22            render(position.parse()); 
23        }
24    }); 
25
26    $BGSContent.mousemove(function(e){
27        if (isPress){
28            scrollDistance = scrollStartX - e.offsetX; 
29            $BGSContent.css('background-position-x', position.offset(e.offsetX - scrollStartX));
30        }
31    }); 
32}

# 完整的实现

代码较长 折叠
000// bgimg-css.js
001var BGS = (function($){
002    var imgList; 
003    
004    var $bgContainer;
005    var change = function(e){ }
006    var _autoplay_id = -1; 
007
008    var BGSClass =  '<style>' +
009    '.BGS-content {' +
010        'background-repeat: no-repeat;' +
011        'width: 100%;' +
012        'height: 100%;' +
013        'background-color: #BBB;' +
014        'display: block;' +
015        'background-position-y: center;' +
016        'cursor: pointer;' + 
017        'background-size: 100%;' + 
018    '}' +
019    '.BGS-transition {' +
020        'transition: all .8s; ' +
021        'transition-timing-function:'; 
022    // cubic-bezier(0.23, 1, 0.32, 1)'}</style>'; 
023
024    var BGSTemplate = ''; 
025    var _config; 
026    var position = []; 
027    var $BGSContent; 
028
029    position.nextPosition = function(){
030        var i = 0;
031        if (this[this.length-1] == 0){
032            this.setPosition(0); 
033            return this; 
034        }
035        for (i=0;i<this.length;i++){
036            this[i]--; 
037        }
038
039        return this; 
040    }
041
042    position.prePosition = function(){
043        var i = 0 ;
044        if (this[0] == 0){
045            console.log('循环'); 
046            this.setPosition(this.length-1);
047            return this; 
048        }
049        for (i=0;i<this.length;i++){
050            this[i]++; 
051        }
052        return this; 
053    }
054
055    position.setPosition = function(id){
056        if (id < 0 || id >= this.length){
057            console.warn('setPosition(id): id越界');
058        } else {
059            this[id] = 0;  // 3 
060            var i = 0;
061            for (i;i<this.length;i++){
062                this[i] = i-id; 
063            }
064        }
065    }
066
067    position.parse = function(){
068        var i = 0; 
069        var temp = ''; 
070        for (i=0;i<this.length;i++){
071            temp += (this[i] * $BGSContent.width()) + 'px, '; 
072        }
073        return temp.substring(0, temp.length-2);
074    }
075
076    position.offset = function(e){
077        var i = 0; 
078        var temp = ''; 
079        for (i=0;i<this.length;i++){
080            temp += (this[i] * $BGSContent.width() + e) + 'px, '; 
081        }
082        return temp.substring(0, temp.length-2);
083    }
084
085    position.where = function(){
086        var i=0;
087        for (i=0;i<this.length;i++){
088            if (this[i] == 0){
089                return i; 
090            }
091        }
092        return -1; 
093    }
094
095    function render(positionx){
096        $BGSContent.css('background-position-x', positionx); 
097        change(position.where()); 
098    }
099
100    function toNext(){
101        position.nextPosition();
102        var temp = position.parse(); 
103        render(temp); 
104    }
105
106    function toPre(){
107        position.prePosition();
108        var temp = position.parse();    
109        render(temp); 
110    }
111
112    function toThere(idx){
113        position.setPosition(idx); 
114        render(position.parse());
115    }
116
117    function setHere(idx){
118        $BGSContent.removeClass('BGS-transition'); 
119        toThere(idx); 
120        setTimeout(function(){
121            $BGSContent.addClass('BGS-transition'); 
122        })
123    }
124
125    var _direction; 
126    function start(todo){
127        _direction = todo; 
128        _autoplay_id = setInterval(todo, _config.autoplay.wait);
129        console.info('BGS 定时器启动 (新方向)');
130    }
131
132    function stop(){
133        clearInterval(_autoplay_id); 
134        console.info('BGS 定时器暂停');
135    }
136
137    function restart(){
138        _autoplay_id = setInterval(_direction, _config.autoplay.wait);
139        console.info('BGS 定时器重启');
140    }
141
142    var init = function(config){
143        _config = config; 
144        var BGSContentSelctor;
145        imgList = config.imgList; 
146
147        if (config.id){
148            BGSTemplate = '<span id="' + config.id + '"class="BGS-content BGS-transition"></span>';
149            BGSContentSelctor = '#'+config.id; 
150        } else {
151            BGSTemplate = '<span class="BGS-content BGS-transition"></span>';
152            BGSContentSelctor = '.BGS-content'; 
153        }
154
155        if (config.change) {
156            change = config.change; 
157        }
158
159        var time_temp = 5000; 
160        if (config.autoplay) {
161            if (config.autoplay === true) { config.autoplay = {} } 
162            if (config.autoplay.wait){
163                time_temp = config.autoplay.wait; 
164            } else {
165                config.autoplay.wait = time_temp; 
166                console.log(config.autoplay.wait); 
167            }
168            if (config.autoplay.direction == 'toPre'){
169                // _autoplay_id = setInterval(toPre, time_temp);
170                start(toPre); 
171            } else { // default toNext 
172                config.autoplay.direction = 'toNext'; 
173                start(toNext); 
174                // _autoplay_id = setInterval(toNext, time_temp); 
175            }
176        }
177
178        if (config.timingFunction){
179            BGSClass += config.timingFunction + ';}</style>'; 
180        } else {
181            BGSClass += 'cubic-bezier(0.23, 1, 0.32, 1);}</style>'
182        }
183        
184        var i = 0; 
185        var BGImage = '';
186        for (i=0;i<imgList.length;i++){
187            position.push(i); 
188            BGImage += 'url('+imgList[i]+'), ';
189        }
190        BGImage = BGImage.substring(0, BGImage.length-2);
191
192        $bgContainer = $(config.selector); 
193        $bgContainer.append(BGSClass); 
194        $bgContainer.append(BGSTemplate); 
195        $BGSContent = $(BGSContentSelctor); 
196        $BGSContent.css('background-image', BGImage); 
197
198        scrollInit(); 
199
200        render(position.parse()); 
201
202        if (config.BGInit){
203            config.BGInit.call(this); 
204        }
205    }
206
207    
208    function scrollInit(){
209        // swipeLeft, swipeRight
210        // $BGSContent.swipe(function(e){
211        //  console.log(e); 
212        // }); 
213        var isPress = false; 
214        var scrollStartX = false;   
215        var judgeWidth;
216        var scrollDistance = 0; 
217        $BGSContent.mousedown(function(e){
218            isPress = true; 
219            scrollStartX = e.offsetX; 
220            judgeWidth = $BGSContent.width() / 10;
221
222            $BGSContent.removeClass('BGS-transition'); 
223        }); 
224        $BGSContent.mouseup(function(){
225            isPress = false; 
226            scrollStartX = false; 
227            // alert('1');
228            $BGSContent.addClass('BGS-transition'); 
229            if (Math.abs(scrollDistance) > judgeWidth) {
230                if (scrollDistance > 0){
231                    toNext(); 
232                } else {
233                    toPre(); 
234                }
235            } else {
236                render(position.parse()); 
237            }
238        }); 
239
240        $BGSContent.mousemove(function(e){
241            if (isPress){
242                scrollDistance = scrollStartX - e.offsetX; 
243                $BGSContent.css('background-position-x', position.offset(e.offsetX - scrollStartX));
244            }
245        }); 
246    }
247
248    return {
249        init: init,
250        toNext: toNext,
251        toPre: toPre,
252        toThere: toThere,
253        setHere: setHere,
254        change: change,
255        stop: stop,
256        start: start,
257        restart: restart
258    }
259})(jQuery);

# 不足

  1. 没有做 transitionFinish 回调
  2. position 数据层的实现不够函数式
  3. 虽然有做 id 但是不能实现多个轮播实例
EOF
EOF




回到顶部