carousel.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. // Generated by CoffeeScript 2.0.0-beta4
  2. (function() {
  3. // ------------------------------------------------------------------------
  4. // 變數與常數設置
  5. // ------------------------------------------------------------------------
  6. // 模組名稱。
  7. var ClassName, Direction, EVENT_NAMESPACE, Error, Event, MODULE_NAMESPACE, Metadata, NAME, Selector, Settings;
  8. NAME = 'carousel';
  9. // 模組事件鍵名。
  10. EVENT_NAMESPACE = `.${NAME}`;
  11. // 模組命名空間。
  12. MODULE_NAMESPACE = `module-${NAME}`;
  13. // 模組設定。
  14. Settings = {
  15. // 消音所有提示,甚至是錯誤訊息。
  16. silent: false,
  17. // 顯示除錯訊息。
  18. debug: true,
  19. // 監聽 DOM 結構異動並自動重整快取。
  20. observeChanges: true,
  21. // 幻燈片換到下一張的毫秒相隔時間。
  22. interval: 4000,
  23. // 是否要自動播放。
  24. autoplay: true,
  25. // 當幻燈片變更時所呼叫的函式。
  26. onChange: () => {},
  27. // 指示器選項。
  28. indicator: {
  29. // 指示器的外觀,`rounded` 為圓角矩形,`circular` 為圓形。
  30. style: 'rounded',
  31. // 是否可供轉跳。
  32. navigable: true,
  33. // 是否疊加在幻燈片上。
  34. overlapped: true
  35. },
  36. // 控制器選項。
  37. control: {
  38. // 控制選項的樣式,`compact` 為較小的按鈕,`full` 為整個側邊區塊
  39. size: 'compact',
  40. // 是否疊加在幻燈片上。
  41. overlapped: true,
  42. // 圖示選項。
  43. icon: {
  44. // 左圖示的圖示名稱。
  45. left: 'chevron left',
  46. // 右圖示的圖示名稱
  47. right: 'chevron right'
  48. }
  49. }
  50. };
  51. // 中繼資料名稱。
  52. Metadata = {
  53. SLIDING: 'sliding',
  54. INDEX: 'index',
  55. CONTENT: 'content',
  56. AUTOPLAY: 'autoplay'
  57. };
  58. // 事件名稱。
  59. Event = {
  60. CHANGE: `change${EVENT_NAMESPACE}`,
  61. CLICK: `click${EVENT_NAMESPACE}`
  62. };
  63. // 方向名稱
  64. Direction = {
  65. NEXT: 'next',
  66. PREVIOUS: 'previous',
  67. LEFT: 'left',
  68. RIGHT: 'right'
  69. };
  70. // 樣式名稱。
  71. ClassName = {
  72. COMPACT: 'compact',
  73. ACTIVE: 'active',
  74. ITEMS: 'items',
  75. ITEM: 'item',
  76. OVERLAPPED: 'overlapped',
  77. CONTROLS: 'controls',
  78. NAVIGABLE: 'navigable',
  79. ROUNDED: 'rounded',
  80. CIRCULAR: 'circular',
  81. INDICATORS: 'indicators',
  82. MOVING: 'moving',
  83. LEFT: 'left',
  84. RIGHT: 'right',
  85. NEXT: 'next',
  86. PREVIOUS: 'previous'
  87. };
  88. // 選擇器名稱。
  89. Selector = {
  90. ITEM: '.item',
  91. CHILD_ITEM: ':scope > .item',
  92. CONTROLS_LEFT: '.controls > .left',
  93. CONTROLS_RIGHT: '.controls > .right',
  94. ITEMS_ITEM: '.items > .item',
  95. ACTIVE_ITEM: '.items > .item.active',
  96. FIRST_ITEM: '.items > .item:first-child',
  97. LAST_ITEM: '.items > .item:last-child',
  98. INDICATORS_ITEM: '.indicators > .item'
  99. };
  100. // 錯誤訊息。
  101. Error = {};
  102. // ------------------------------------------------------------------------
  103. // 模組註冊
  104. // ------------------------------------------------------------------------
  105. ts.register({NAME, MODULE_NAMESPACE, Error, Settings}, ({$allModules, $this, element, debug, settings}) => {
  106. var duration, module;
  107. // ------------------------------------------------------------------------
  108. // 區域變數
  109. // ------------------------------------------------------------------------
  110. duration = 700;
  111. // ------------------------------------------------------------------------
  112. // 模組定義
  113. // ------------------------------------------------------------------------
  114. return module = {
  115. play: () => {
  116. debug('播放幻燈片', element);
  117. if (module.has.timer()) {
  118. module.restart.timer();
  119. } else {
  120. module.set.timer();
  121. }
  122. return $allModules;
  123. },
  124. pause: () => {
  125. debug('暫停幻燈片', element);
  126. module.stop.timer();
  127. return $allModules;
  128. },
  129. next: () => {
  130. debug('下一張幻燈片', element);
  131. module.goto(Direction.NEXT);
  132. return $allModules;
  133. },
  134. previous: () => {
  135. debug('下一張幻燈片', element);
  136. module.goto(Direction.PREVIOUS);
  137. return $allModules;
  138. },
  139. animate: {
  140. move: (callback, $to) => {
  141. return module.get.$current().off('transitionend').one('transitionend', () => {
  142. module.get.$items().removeClass(ClassName.MOVING).removeClass(ClassName.ACTIVE);
  143. if (callback != null) {
  144. return callback.call();
  145. }
  146. }).emulate('transitionend', duration);
  147. }
  148. },
  149. goto: (direction, index) => {
  150. var $to;
  151. debug('幻燈片往指定方向切換', direction, index, element);
  152. if (module.is.sliding()) {
  153. return;
  154. }
  155. if (module.has.timer()) {
  156. module.remove.timer();
  157. module.set.timer();
  158. }
  159. switch (false) {
  160. case index == null:
  161. $to = module.get.$item(index);
  162. break;
  163. case direction !== Direction.NEXT:
  164. $to = module.get.next.$item();
  165. break;
  166. case direction !== Direction.PREVIOUS:
  167. $to = module.get.previous.$item();
  168. }
  169. if (!index) {
  170. index = module.get.index($to);
  171. }
  172. module.set.sliding(true);
  173. module.set.direction(direction, $to);
  174. module.set.indicator(index);
  175. module.set.index(index);
  176. module.trigger.change();
  177. return module.animate.move(() => {
  178. module.remove.direction();
  179. module.set.sliding(false);
  180. return module.set.active(index);
  181. });
  182. },
  183. slide: {
  184. to: (index) => {
  185. debug('滑到指定幻燈片索引', index, element);
  186. module.goto(module.get.direction(index), index);
  187. return $allModules;
  188. }
  189. },
  190. get: {
  191. index: ($item) => {
  192. if ($item != null) {
  193. return $item.index();
  194. } else {
  195. return $this.data(Metadata.INDEX);
  196. }
  197. },
  198. direction: (to) => {
  199. if (to > module.get.index()) {
  200. return Direction.NEXT;
  201. } else {
  202. return Direction.PREVIOUS;
  203. }
  204. },
  205. movingDirection: (direction) => {
  206. if (direction === Direction.NEXT) {
  207. return ClassName.LEFT;
  208. } else {
  209. return ClassName.RIGHT;
  210. }
  211. },
  212. html: () => {
  213. return $this.html();
  214. },
  215. content: () => {
  216. return $this.data(Metadata.CONTENT);
  217. },
  218. first: {
  219. $item: () => {
  220. return $this.find(Selector.FIRST_ITEM);
  221. }
  222. },
  223. last: {
  224. $item: () => {
  225. return $this.find(Selector.LAST_ITEM);
  226. }
  227. },
  228. next: {
  229. $item: () => {
  230. var index;
  231. index = module.get.index() + 1;
  232. if (module.has.$item(index)) {
  233. return module.get.$item(index);
  234. } else {
  235. return module.get.first.$item();
  236. }
  237. }
  238. },
  239. previous: {
  240. $item: () => {
  241. var index;
  242. index = module.get.index() - 1;
  243. if (module.has.$item(index)) {
  244. return module.get.$item(index);
  245. } else {
  246. return module.get.last.$item();
  247. }
  248. }
  249. },
  250. $items: () => {
  251. return $this.find(Selector.ITEMS_ITEM);
  252. },
  253. $item: (index) => {
  254. return $this.find(Selector.ITEMS_ITEM).eq(index);
  255. },
  256. $current: () => {
  257. return $this.find(Selector.ACTIVE_ITEM);
  258. },
  259. $indicators: () => {
  260. return $this.find(Selector.INDICATORS_ITEM);
  261. }
  262. },
  263. has: {
  264. timer: () => {
  265. return $this.hasTimer(Metadata.AUTOPLAY);
  266. },
  267. $item: (index) => {
  268. return module.get.$item(index).length > 0;
  269. }
  270. },
  271. restart: {
  272. timer: () => {
  273. return $this.playTimer(Metadata.AUTOPLAY);
  274. }
  275. },
  276. set: {
  277. index: (index) => {
  278. return $this.data(Metadata.INDEX, index);
  279. },
  280. sliding: (bool) => {
  281. return $this.data(Metadata.SLIDING, bool);
  282. },
  283. content: (content) => {
  284. return $this.data(Metadata.CONTENT, content);
  285. },
  286. html: (html) => {
  287. return $this.html(html);
  288. },
  289. timer: () => {
  290. return $this.setTimer({
  291. name: Metadata.AUTOPLAY,
  292. callback: module.next,
  293. interval: settings.interval,
  294. looping: true,
  295. visible: true
  296. });
  297. },
  298. active: (index) => {
  299. return module.get.$item(index).addClass(ClassName.ACTIVE);
  300. },
  301. indicator: (index) => {
  302. return $this.find(Selector.INDICATORS_ITEM).removeClass(ClassName.ACTIVE).eq(index).addClass(ClassName.ACTIVE);
  303. },
  304. direction: (direction, $to) => {
  305. var movingDirection;
  306. movingDirection = module.get.movingDirection(direction);
  307. $to.addClass(direction).repaint().addClass(ClassName.MOVING, movingDirection);
  308. return module.get.$current().addClass(ClassName.MOVING, movingDirection);
  309. }
  310. },
  311. stop: {
  312. timer: () => {
  313. return $this.pauseTimer(Metadata.AUTOPLAY);
  314. }
  315. },
  316. remove: {
  317. timer: () => {
  318. return $this.removeTimer(Metadata.AUTOPLAY);
  319. },
  320. direction: () => {
  321. return module.get.$items().removeClass(ClassName.LEFT, ClassName.RIGHT, ClassName.NEXT, ClassName.PREVIOUS);
  322. },
  323. html: () => {
  324. return module.set.html('');
  325. }
  326. },
  327. should: {
  328. autoplay: () => {
  329. return settings.autoplay;
  330. }
  331. },
  332. is: {
  333. sliding: () => {
  334. return $this.data(Metadata.SLIDING);
  335. }
  336. },
  337. trigger: {
  338. change: () => {
  339. return $this.trigger(Event.CHANGE, element, module.get.index());
  340. }
  341. },
  342. create: {
  343. $control: () => {
  344. return ts('<div>').html(`<a href=\"#!\" class=\"left\"><i class=\"${settings.control.icon.left} icon\"></i></a>\n<a href=\"#!\" class=\"right\"><i class=\"${settings.control.icon.right} icon\"></i></a>`).addClass(ClassName.CONTROLS).addClass({
  345. [`${ClassName.OVERLAPPED}`]: settings.control.overlapped,
  346. [`${ClassName.COMPACT}`]: settings.control.style === ClassName.COMPACT
  347. });
  348. },
  349. $indicators: (amount) => {
  350. var $indicator, $indicators, i, index, ref;
  351. $indicators = ts('<div>').addClass(ClassName.INDICATORS).addClass({
  352. [`${ClassName.OVERLAPPED}`]: settings.indicator.overlapped,
  353. [`${ClassName.NAVIGABLE}`]: settings.indicator.navigable,
  354. [`${ClassName.CIRCULAR}`]: settings.indicator.style !== ClassName.ROUNDED
  355. });
  356. for (index = i = 1, ref = amount; 1 <= ref ? i <= ref : i >= ref; index = 1 <= ref ? ++i : --i) {
  357. $indicator = ts('<div>').addClass(ClassName.ITEM).addClass({
  358. [`${ClassName.ACTIVE}`]: index === 1
  359. });
  360. $indicators.append($indicator);
  361. }
  362. return $indicators;
  363. }
  364. },
  365. bind: {
  366. events: () => {
  367. var ref;
  368. debug('綁定事件', element);
  369. if (settings.control) {
  370. module.bind.control.events();
  371. }
  372. if ((ref = settings.indicator) != null ? ref.navigable : void 0) {
  373. module.bind.indicator.events();
  374. }
  375. return $this.on(Event.CHANGE, (event, context, index) => {
  376. debug("發生 CHANGE 事件", context);
  377. return settings.onChange.call(context, event, index);
  378. });
  379. },
  380. control: {
  381. events: () => {
  382. debug('綁定控制按鈕事件', element);
  383. $this.on(Event.CLICK, Selector.CONTROLS_LEFT, () => {
  384. debug("左控制按鈕發生 CLICK 事件", element);
  385. return module.previous();
  386. });
  387. return $this.on(Event.CLICK, Selector.CONTROLS_RIGHT, () => {
  388. debug("右控制按鈕發生 CLICK 事件", element);
  389. return module.next();
  390. });
  391. }
  392. },
  393. indicator: {
  394. events: () => {
  395. debug('綁定指示器事件', element);
  396. return module.get.$indicators().each((element, index) => {
  397. return ts(element).on(Event.CLICK, () => {
  398. debug("指示器發生 CLICK 事件", element);
  399. return module.slide.to(index);
  400. });
  401. });
  402. }
  403. }
  404. },
  405. // ------------------------------------------------------------------------
  406. // 基礎方法
  407. // ------------------------------------------------------------------------
  408. initialize: () => {
  409. var $children, $items;
  410. debug('初始化幻燈片', element);
  411. module.set.content(module.get.html());
  412. $children = $this.find(Selector.CHILD_ITEM);
  413. $items = ts('<div>').addClass(ClassName.ITEMS).append($children);
  414. module.remove.html();
  415. if (settings.control) {
  416. $this.append(module.create.$control());
  417. }
  418. $this.append($items);
  419. if (settings.indicator) {
  420. $this.append(module.create.$indicators($children.length));
  421. }
  422. module.set.active(0);
  423. module.set.index(0);
  424. module.bind.events();
  425. if (module.should.autoplay()) {
  426. return module.play();
  427. }
  428. },
  429. instantiate: () => {
  430. return debug('實例化幻燈片', element);
  431. },
  432. refresh: () => {
  433. return $allModules;
  434. },
  435. destroy: () => {
  436. debug('摧毀幻燈片', element);
  437. module.remove.timer();
  438. module.set.html(module.get.content());
  439. $this.removeData(MODULE_NAMESPACE).off(EVENT_NAMESPACE);
  440. return $allModules;
  441. }
  442. };
  443. });
  444. }).call(this);