前些日子自己做了个轮播组件,中间过程感觉挺有趣,便写了下来。
起名 BGS.js (BackGroundSlide.js)
设想:
- 调用
BGS.init
初始化后返回 BGS实例 - 等待操作 (上一张 下一张等等)
# 架构原理 ↵
# 选用 CSS 实现
依我所见,有两种比较可行的方式可以实现图片轮播
- 利用 img 标签 ( absolute, left: 0% => 100%, 100% => 0 )
- 利用 CSS background 属性 ( backgorund-position-x )
我选择第二种方案作为实施方向。
# 关于 background 属性
- 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}
- background-position-x 也可以接受多个参数,而且跟
background-image
一一对应 - transition 对
background
也适用 - 利用 $(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 上还期望有以下方法:
- init: 初始化,并传入配置, **配置的变量名在init里是 config 在BGS上是 _config **
- toNext: 立即切换到下一张
- toPre: 立即切换到上一张
- toThere: 立即切换到第 N 张 (带动画效果)
- setHere: 立即切换到第 N 张 (不带动画效果,瞬变)
- change: 图片切换的时候的回调,回调会接受一个参数 这个参数是新位点
- stop: 停止自动播放
- start: 开始自动播放,需要传递配置参数
- 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);
DEMO: demox - BGS.js
# 不足 ↵
- 没有做
transitionFinish
回调 position
数据层的实现不够函数式- 虽然有做
id
但是不能实现多个轮播实例

EOF