scrollbar.js 9.9 KB

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