scrollbar.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import { getDocument } from 'ssr-window';
  2. import $ from '../../shared/dom.js';
  3. import { nextTick } from '../../shared/utils.js';
  4. import createElementIfNotDefined from '../../shared/create-element-if-not-defined.js';
  5. export default function Scrollbar({
  6. swiper,
  7. extendParams,
  8. on,
  9. emit
  10. }) {
  11. const document = getDocument();
  12. let isTouched = false;
  13. let timeout = null;
  14. let dragTimeout = null;
  15. let dragStartPos;
  16. let dragSize;
  17. let trackSize;
  18. let divider;
  19. extendParams({
  20. scrollbar: {
  21. el: null,
  22. dragSize: 'auto',
  23. hide: false,
  24. draggable: false,
  25. snapOnRelease: true,
  26. lockClass: 'swiper-scrollbar-lock',
  27. dragClass: 'swiper-scrollbar-drag',
  28. scrollbarDisabledClass: 'swiper-scrollbar-disabled',
  29. horizontalClass: `swiper-scrollbar-horizontal`,
  30. verticalClass: `swiper-scrollbar-vertical`
  31. }
  32. });
  33. swiper.scrollbar = {
  34. el: null,
  35. dragEl: null,
  36. $el: null,
  37. $dragEl: null
  38. };
  39. function setTranslate() {
  40. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  41. const {
  42. scrollbar,
  43. rtlTranslate: rtl,
  44. progress
  45. } = swiper;
  46. const {
  47. $dragEl,
  48. $el
  49. } = scrollbar;
  50. const params = swiper.params.scrollbar;
  51. let newSize = dragSize;
  52. let newPos = (trackSize - dragSize) * progress;
  53. if (rtl) {
  54. newPos = -newPos;
  55. if (newPos > 0) {
  56. newSize = dragSize - newPos;
  57. newPos = 0;
  58. } else if (-newPos + dragSize > trackSize) {
  59. newSize = trackSize + newPos;
  60. }
  61. } else if (newPos < 0) {
  62. newSize = dragSize + newPos;
  63. newPos = 0;
  64. } else if (newPos + dragSize > trackSize) {
  65. newSize = trackSize - newPos;
  66. }
  67. if (swiper.isHorizontal()) {
  68. $dragEl.transform(`translate3d(${newPos}px, 0, 0)`);
  69. $dragEl[0].style.width = `${newSize}px`;
  70. } else {
  71. $dragEl.transform(`translate3d(0px, ${newPos}px, 0)`);
  72. $dragEl[0].style.height = `${newSize}px`;
  73. }
  74. if (params.hide) {
  75. clearTimeout(timeout);
  76. $el[0].style.opacity = 1;
  77. timeout = setTimeout(() => {
  78. $el[0].style.opacity = 0;
  79. $el.transition(400);
  80. }, 1000);
  81. }
  82. }
  83. function setTransition(duration) {
  84. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  85. swiper.scrollbar.$dragEl.transition(duration);
  86. }
  87. function updateSize() {
  88. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  89. const {
  90. scrollbar
  91. } = swiper;
  92. const {
  93. $dragEl,
  94. $el
  95. } = scrollbar;
  96. $dragEl[0].style.width = '';
  97. $dragEl[0].style.height = '';
  98. trackSize = swiper.isHorizontal() ? $el[0].offsetWidth : $el[0].offsetHeight;
  99. divider = swiper.size / (swiper.virtualSize + swiper.params.slidesOffsetBefore - (swiper.params.centeredSlides ? swiper.snapGrid[0] : 0));
  100. if (swiper.params.scrollbar.dragSize === 'auto') {
  101. dragSize = trackSize * divider;
  102. } else {
  103. dragSize = parseInt(swiper.params.scrollbar.dragSize, 10);
  104. }
  105. if (swiper.isHorizontal()) {
  106. $dragEl[0].style.width = `${dragSize}px`;
  107. } else {
  108. $dragEl[0].style.height = `${dragSize}px`;
  109. }
  110. if (divider >= 1) {
  111. $el[0].style.display = 'none';
  112. } else {
  113. $el[0].style.display = '';
  114. }
  115. if (swiper.params.scrollbar.hide) {
  116. $el[0].style.opacity = 0;
  117. }
  118. if (swiper.params.watchOverflow && swiper.enabled) {
  119. scrollbar.$el[swiper.isLocked ? 'addClass' : 'removeClass'](swiper.params.scrollbar.lockClass);
  120. }
  121. }
  122. function getPointerPosition(e) {
  123. if (swiper.isHorizontal()) {
  124. return e.type === 'touchstart' || e.type === 'touchmove' ? e.targetTouches[0].clientX : e.clientX;
  125. }
  126. return e.type === 'touchstart' || e.type === 'touchmove' ? e.targetTouches[0].clientY : e.clientY;
  127. }
  128. function setDragPosition(e) {
  129. const {
  130. scrollbar,
  131. rtlTranslate: rtl
  132. } = swiper;
  133. const {
  134. $el
  135. } = scrollbar;
  136. let positionRatio;
  137. positionRatio = (getPointerPosition(e) - $el.offset()[swiper.isHorizontal() ? 'left' : 'top'] - (dragStartPos !== null ? dragStartPos : dragSize / 2)) / (trackSize - dragSize);
  138. positionRatio = Math.max(Math.min(positionRatio, 1), 0);
  139. if (rtl) {
  140. positionRatio = 1 - positionRatio;
  141. }
  142. const position = swiper.minTranslate() + (swiper.maxTranslate() - swiper.minTranslate()) * positionRatio;
  143. swiper.updateProgress(position);
  144. swiper.setTranslate(position);
  145. swiper.updateActiveIndex();
  146. swiper.updateSlidesClasses();
  147. }
  148. function onDragStart(e) {
  149. const params = swiper.params.scrollbar;
  150. const {
  151. scrollbar,
  152. $wrapperEl
  153. } = swiper;
  154. const {
  155. $el,
  156. $dragEl
  157. } = scrollbar;
  158. isTouched = true;
  159. dragStartPos = e.target === $dragEl[0] || e.target === $dragEl ? getPointerPosition(e) - e.target.getBoundingClientRect()[swiper.isHorizontal() ? 'left' : 'top'] : null;
  160. e.preventDefault();
  161. e.stopPropagation();
  162. $wrapperEl.transition(100);
  163. $dragEl.transition(100);
  164. setDragPosition(e);
  165. clearTimeout(dragTimeout);
  166. $el.transition(0);
  167. if (params.hide) {
  168. $el.css('opacity', 1);
  169. }
  170. if (swiper.params.cssMode) {
  171. swiper.$wrapperEl.css('scroll-snap-type', 'none');
  172. }
  173. emit('scrollbarDragStart', e);
  174. }
  175. function onDragMove(e) {
  176. const {
  177. scrollbar,
  178. $wrapperEl
  179. } = swiper;
  180. const {
  181. $el,
  182. $dragEl
  183. } = scrollbar;
  184. if (!isTouched) return;
  185. if (e.preventDefault) e.preventDefault();else e.returnValue = false;
  186. setDragPosition(e);
  187. $wrapperEl.transition(0);
  188. $el.transition(0);
  189. $dragEl.transition(0);
  190. emit('scrollbarDragMove', e);
  191. }
  192. function onDragEnd(e) {
  193. const params = swiper.params.scrollbar;
  194. const {
  195. scrollbar,
  196. $wrapperEl
  197. } = swiper;
  198. const {
  199. $el
  200. } = scrollbar;
  201. if (!isTouched) return;
  202. isTouched = false;
  203. if (swiper.params.cssMode) {
  204. swiper.$wrapperEl.css('scroll-snap-type', '');
  205. $wrapperEl.transition('');
  206. }
  207. if (params.hide) {
  208. clearTimeout(dragTimeout);
  209. dragTimeout = nextTick(() => {
  210. $el.css('opacity', 0);
  211. $el.transition(400);
  212. }, 1000);
  213. }
  214. emit('scrollbarDragEnd', e);
  215. if (params.snapOnRelease) {
  216. swiper.slideToClosest();
  217. }
  218. }
  219. function events(method) {
  220. const {
  221. scrollbar,
  222. touchEventsTouch,
  223. touchEventsDesktop,
  224. params,
  225. support
  226. } = swiper;
  227. const $el = scrollbar.$el;
  228. if (!$el) return;
  229. const target = $el[0];
  230. const activeListener = support.passiveListener && params.passiveListeners ? {
  231. passive: false,
  232. capture: false
  233. } : false;
  234. const passiveListener = support.passiveListener && params.passiveListeners ? {
  235. passive: true,
  236. capture: false
  237. } : false;
  238. if (!target) return;
  239. const eventMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
  240. if (!support.touch) {
  241. target[eventMethod](touchEventsDesktop.start, onDragStart, activeListener);
  242. document[eventMethod](touchEventsDesktop.move, onDragMove, activeListener);
  243. document[eventMethod](touchEventsDesktop.end, onDragEnd, passiveListener);
  244. } else {
  245. target[eventMethod](touchEventsTouch.start, onDragStart, activeListener);
  246. target[eventMethod](touchEventsTouch.move, onDragMove, activeListener);
  247. target[eventMethod](touchEventsTouch.end, onDragEnd, passiveListener);
  248. }
  249. }
  250. function enableDraggable() {
  251. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  252. events('on');
  253. }
  254. function disableDraggable() {
  255. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  256. events('off');
  257. }
  258. function init() {
  259. const {
  260. scrollbar,
  261. $el: $swiperEl
  262. } = swiper;
  263. swiper.params.scrollbar = createElementIfNotDefined(swiper, swiper.originalParams.scrollbar, swiper.params.scrollbar, {
  264. el: 'swiper-scrollbar'
  265. });
  266. const params = swiper.params.scrollbar;
  267. if (!params.el) return;
  268. let $el = $(params.el);
  269. if (swiper.params.uniqueNavElements && typeof params.el === 'string' && $el.length > 1 && $swiperEl.find(params.el).length === 1) {
  270. $el = $swiperEl.find(params.el);
  271. }
  272. $el.addClass(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
  273. let $dragEl = $el.find(`.${swiper.params.scrollbar.dragClass}`);
  274. if ($dragEl.length === 0) {
  275. $dragEl = $(`<div class="${swiper.params.scrollbar.dragClass}"></div>`);
  276. $el.append($dragEl);
  277. }
  278. Object.assign(scrollbar, {
  279. $el,
  280. el: $el[0],
  281. $dragEl,
  282. dragEl: $dragEl[0]
  283. });
  284. if (params.draggable) {
  285. enableDraggable();
  286. }
  287. if ($el) {
  288. $el[swiper.enabled ? 'removeClass' : 'addClass'](swiper.params.scrollbar.lockClass);
  289. }
  290. }
  291. function destroy() {
  292. const params = swiper.params.scrollbar;
  293. const $el = swiper.scrollbar.$el;
  294. if ($el) {
  295. $el.removeClass(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
  296. }
  297. disableDraggable();
  298. }
  299. on('init', () => {
  300. if (swiper.params.scrollbar.enabled === false) {
  301. // eslint-disable-next-line
  302. disable();
  303. } else {
  304. init();
  305. updateSize();
  306. setTranslate();
  307. }
  308. });
  309. on('update resize observerUpdate lock unlock', () => {
  310. updateSize();
  311. });
  312. on('setTranslate', () => {
  313. setTranslate();
  314. });
  315. on('setTransition', (_s, duration) => {
  316. setTransition(duration);
  317. });
  318. on('enable disable', () => {
  319. const {
  320. $el
  321. } = swiper.scrollbar;
  322. if ($el) {
  323. $el[swiper.enabled ? 'removeClass' : 'addClass'](swiper.params.scrollbar.lockClass);
  324. }
  325. });
  326. on('destroy', () => {
  327. destroy();
  328. });
  329. const enable = () => {
  330. swiper.$el.removeClass(swiper.params.scrollbar.scrollbarDisabledClass);
  331. if (swiper.scrollbar.$el) {
  332. swiper.scrollbar.$el.removeClass(swiper.params.scrollbar.scrollbarDisabledClass);
  333. }
  334. init();
  335. updateSize();
  336. setTranslate();
  337. };
  338. const disable = () => {
  339. swiper.$el.addClass(swiper.params.scrollbar.scrollbarDisabledClass);
  340. if (swiper.scrollbar.$el) {
  341. swiper.scrollbar.$el.addClass(swiper.params.scrollbar.scrollbarDisabledClass);
  342. }
  343. destroy();
  344. };
  345. Object.assign(swiper.scrollbar, {
  346. enable,
  347. disable,
  348. updateSize,
  349. setTranslate,
  350. init,
  351. destroy
  352. });
  353. }