scrollbar.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. }
  29. });
  30. swiper.scrollbar = {
  31. el: null,
  32. dragEl: null,
  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. progress
  42. } = swiper;
  43. const {
  44. $dragEl,
  45. $el
  46. } = scrollbar;
  47. const params = swiper.params.scrollbar;
  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.transform(`translate3d(${newPos}px, 0, 0)`);
  66. $dragEl[0].style.width = `${newSize}px`;
  67. } else {
  68. $dragEl.transform(`translate3d(0px, ${newPos}px, 0)`);
  69. $dragEl[0].style.height = `${newSize}px`;
  70. }
  71. if (params.hide) {
  72. clearTimeout(timeout);
  73. $el[0].style.opacity = 1;
  74. timeout = setTimeout(() => {
  75. $el[0].style.opacity = 0;
  76. $el.transition(400);
  77. }, 1000);
  78. }
  79. }
  80. function setTransition(duration) {
  81. if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
  82. swiper.scrollbar.$dragEl.transition(duration);
  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[0].style.width = '';
  94. $dragEl[0].style.height = '';
  95. trackSize = swiper.isHorizontal() ? $el[0].offsetWidth : $el[0].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[0].style.width = `${dragSize}px`;
  104. } else {
  105. $dragEl[0].style.height = `${dragSize}px`;
  106. }
  107. if (divider >= 1) {
  108. $el[0].style.display = 'none';
  109. } else {
  110. $el[0].style.display = '';
  111. }
  112. if (swiper.params.scrollbar.hide) {
  113. $el[0].style.opacity = 0;
  114. }
  115. if (swiper.params.watchOverflow && swiper.enabled) {
  116. scrollbar.$el[swiper.isLocked ? 'addClass' : 'removeClass'](swiper.params.scrollbar.lockClass);
  117. }
  118. }
  119. function getPointerPosition(e) {
  120. if (swiper.isHorizontal()) {
  121. return e.type === 'touchstart' || e.type === 'touchmove' ? e.targetTouches[0].clientX : e.clientX;
  122. }
  123. return e.type === 'touchstart' || e.type === 'touchmove' ? e.targetTouches[0].clientY : e.clientY;
  124. }
  125. function setDragPosition(e) {
  126. const {
  127. scrollbar,
  128. rtlTranslate: rtl
  129. } = swiper;
  130. const {
  131. $el
  132. } = scrollbar;
  133. let positionRatio;
  134. positionRatio = (getPointerPosition(e) - $el.offset()[swiper.isHorizontal() ? 'left' : 'top'] - (dragStartPos !== null ? dragStartPos : dragSize / 2)) / (trackSize - dragSize);
  135. positionRatio = Math.max(Math.min(positionRatio, 1), 0);
  136. if (rtl) {
  137. positionRatio = 1 - positionRatio;
  138. }
  139. const position = swiper.minTranslate() + (swiper.maxTranslate() - swiper.minTranslate()) * positionRatio;
  140. swiper.updateProgress(position);
  141. swiper.setTranslate(position);
  142. swiper.updateActiveIndex();
  143. swiper.updateSlidesClasses();
  144. }
  145. function onDragStart(e) {
  146. const params = swiper.params.scrollbar;
  147. const {
  148. scrollbar,
  149. $wrapperEl
  150. } = swiper;
  151. const {
  152. $el,
  153. $dragEl
  154. } = scrollbar;
  155. isTouched = true;
  156. dragStartPos = e.target === $dragEl[0] || e.target === $dragEl ? getPointerPosition(e) - e.target.getBoundingClientRect()[swiper.isHorizontal() ? 'left' : 'top'] : null;
  157. e.preventDefault();
  158. e.stopPropagation();
  159. $wrapperEl.transition(100);
  160. $dragEl.transition(100);
  161. setDragPosition(e);
  162. clearTimeout(dragTimeout);
  163. $el.transition(0);
  164. if (params.hide) {
  165. $el.css('opacity', 1);
  166. }
  167. if (swiper.params.cssMode) {
  168. swiper.$wrapperEl.css('scroll-snap-type', 'none');
  169. }
  170. emit('scrollbarDragStart', e);
  171. }
  172. function onDragMove(e) {
  173. const {
  174. scrollbar,
  175. $wrapperEl
  176. } = swiper;
  177. const {
  178. $el,
  179. $dragEl
  180. } = scrollbar;
  181. if (!isTouched) return;
  182. if (e.preventDefault) e.preventDefault();else e.returnValue = false;
  183. setDragPosition(e);
  184. $wrapperEl.transition(0);
  185. $el.transition(0);
  186. $dragEl.transition(0);
  187. emit('scrollbarDragMove', e);
  188. }
  189. function onDragEnd(e) {
  190. const params = swiper.params.scrollbar;
  191. const {
  192. scrollbar,
  193. $wrapperEl
  194. } = swiper;
  195. const {
  196. $el
  197. } = scrollbar;
  198. if (!isTouched) return;
  199. isTouched = false;
  200. if (swiper.params.cssMode) {
  201. swiper.$wrapperEl.css('scroll-snap-type', '');
  202. $wrapperEl.transition('');
  203. }
  204. if (params.hide) {
  205. clearTimeout(dragTimeout);
  206. dragTimeout = nextTick(() => {
  207. $el.css('opacity', 0);
  208. $el.transition(400);
  209. }, 1000);
  210. }
  211. emit('scrollbarDragEnd', e);
  212. if (params.snapOnRelease) {
  213. swiper.slideToClosest();
  214. }
  215. }
  216. function events(method) {
  217. const {
  218. scrollbar,
  219. touchEventsTouch,
  220. touchEventsDesktop,
  221. params,
  222. support
  223. } = swiper;
  224. const $el = scrollbar.$el;
  225. const target = $el[0];
  226. const activeListener = support.passiveListener && params.passiveListeners ? {
  227. passive: false,
  228. capture: false
  229. } : false;
  230. const passiveListener = support.passiveListener && params.passiveListeners ? {
  231. passive: true,
  232. capture: false
  233. } : false;
  234. if (!target) return;
  235. const eventMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
  236. if (!support.touch) {
  237. target[eventMethod](touchEventsDesktop.start, onDragStart, activeListener);
  238. document[eventMethod](touchEventsDesktop.move, onDragMove, activeListener);
  239. document[eventMethod](touchEventsDesktop.end, onDragEnd, passiveListener);
  240. } else {
  241. target[eventMethod](touchEventsTouch.start, onDragStart, activeListener);
  242. target[eventMethod](touchEventsTouch.move, onDragMove, activeListener);
  243. target[eventMethod](touchEventsTouch.end, onDragEnd, passiveListener);
  244. }
  245. }
  246. function enableDraggable() {
  247. if (!swiper.params.scrollbar.el) return;
  248. events('on');
  249. }
  250. function disableDraggable() {
  251. if (!swiper.params.scrollbar.el) return;
  252. events('off');
  253. }
  254. function init() {
  255. const {
  256. scrollbar,
  257. $el: $swiperEl
  258. } = swiper;
  259. swiper.params.scrollbar = createElementIfNotDefined(swiper, swiper.originalParams.scrollbar, swiper.params.scrollbar, {
  260. el: 'swiper-scrollbar'
  261. });
  262. const params = swiper.params.scrollbar;
  263. if (!params.el) return;
  264. let $el = $(params.el);
  265. if (swiper.params.uniqueNavElements && typeof params.el === 'string' && $el.length > 1 && $swiperEl.find(params.el).length === 1) {
  266. $el = $swiperEl.find(params.el);
  267. }
  268. let $dragEl = $el.find(`.${swiper.params.scrollbar.dragClass}`);
  269. if ($dragEl.length === 0) {
  270. $dragEl = $(`<div class="${swiper.params.scrollbar.dragClass}"></div>`);
  271. $el.append($dragEl);
  272. }
  273. Object.assign(scrollbar, {
  274. $el,
  275. el: $el[0],
  276. $dragEl,
  277. dragEl: $dragEl[0]
  278. });
  279. if (params.draggable) {
  280. enableDraggable();
  281. }
  282. if ($el) {
  283. $el[swiper.enabled ? 'removeClass' : 'addClass'](swiper.params.scrollbar.lockClass);
  284. }
  285. }
  286. function destroy() {
  287. disableDraggable();
  288. }
  289. on('init', () => {
  290. init();
  291. updateSize();
  292. setTranslate();
  293. });
  294. on('update resize observerUpdate lock unlock', () => {
  295. updateSize();
  296. });
  297. on('setTranslate', () => {
  298. setTranslate();
  299. });
  300. on('setTransition', (_s, duration) => {
  301. setTransition(duration);
  302. });
  303. on('enable disable', () => {
  304. const {
  305. $el
  306. } = swiper.scrollbar;
  307. if ($el) {
  308. $el[swiper.enabled ? 'removeClass' : 'addClass'](swiper.params.scrollbar.lockClass);
  309. }
  310. });
  311. on('destroy', () => {
  312. destroy();
  313. });
  314. Object.assign(swiper.scrollbar, {
  315. updateSize,
  316. setTranslate,
  317. init,
  318. destroy
  319. });
  320. }