index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. // Utils
  2. import { createNamespace } from '../utils';
  3. import { isHidden } from '../utils/dom/style';
  4. import { preventDefault } from '../utils/dom/event';
  5. import { doubleRaf } from '../utils/dom/raf';
  6. import { range } from '../utils/format/number'; // Mixins
  7. import { TouchMixin } from '../mixins/touch';
  8. import { ParentMixin } from '../mixins/relation';
  9. import { BindEventMixin } from '../mixins/bind-event';
  10. var _createNamespace = createNamespace('swipe'),
  11. createComponent = _createNamespace[0],
  12. bem = _createNamespace[1];
  13. export default createComponent({
  14. mixins: [TouchMixin, ParentMixin('vanSwipe'), BindEventMixin(function (bind, isBind) {
  15. bind(window, 'resize', this.resize, true);
  16. bind(window, 'orientationchange', this.resize, true);
  17. bind(window, 'visibilitychange', this.onVisibilityChange);
  18. if (isBind) {
  19. this.initialize();
  20. } else {
  21. this.clear();
  22. }
  23. })],
  24. props: {
  25. width: [Number, String],
  26. height: [Number, String],
  27. autoplay: [Number, String],
  28. vertical: Boolean,
  29. lazyRender: Boolean,
  30. indicatorColor: String,
  31. loop: {
  32. type: Boolean,
  33. default: true
  34. },
  35. duration: {
  36. type: [Number, String],
  37. default: 500
  38. },
  39. touchable: {
  40. type: Boolean,
  41. default: true
  42. },
  43. initialSwipe: {
  44. type: [Number, String],
  45. default: 0
  46. },
  47. showIndicators: {
  48. type: Boolean,
  49. default: true
  50. },
  51. stopPropagation: {
  52. type: Boolean,
  53. default: true
  54. }
  55. },
  56. data: function data() {
  57. return {
  58. rect: null,
  59. offset: 0,
  60. active: 0,
  61. deltaX: 0,
  62. deltaY: 0,
  63. swiping: false,
  64. computedWidth: 0,
  65. computedHeight: 0
  66. };
  67. },
  68. watch: {
  69. children: function children() {
  70. this.initialize();
  71. },
  72. initialSwipe: function initialSwipe() {
  73. this.initialize();
  74. },
  75. autoplay: function autoplay(_autoplay) {
  76. if (_autoplay > 0) {
  77. this.autoPlay();
  78. } else {
  79. this.clear();
  80. }
  81. }
  82. },
  83. computed: {
  84. count: function count() {
  85. return this.children.length;
  86. },
  87. maxCount: function maxCount() {
  88. return Math.ceil(Math.abs(this.minOffset) / this.size);
  89. },
  90. delta: function delta() {
  91. return this.vertical ? this.deltaY : this.deltaX;
  92. },
  93. size: function size() {
  94. return this[this.vertical ? 'computedHeight' : 'computedWidth'];
  95. },
  96. trackSize: function trackSize() {
  97. return this.count * this.size;
  98. },
  99. activeIndicator: function activeIndicator() {
  100. return (this.active + this.count) % this.count;
  101. },
  102. isCorrectDirection: function isCorrectDirection() {
  103. var expect = this.vertical ? 'vertical' : 'horizontal';
  104. return this.direction === expect;
  105. },
  106. trackStyle: function trackStyle() {
  107. var style = {
  108. transitionDuration: (this.swiping ? 0 : this.duration) + "ms",
  109. transform: "translate" + (this.vertical ? 'Y' : 'X') + "(" + this.offset + "px)"
  110. };
  111. if (this.size) {
  112. var mainAxis = this.vertical ? 'height' : 'width';
  113. var crossAxis = this.vertical ? 'width' : 'height';
  114. style[mainAxis] = this.trackSize + "px";
  115. style[crossAxis] = this[crossAxis] ? this[crossAxis] + "px" : '';
  116. }
  117. return style;
  118. },
  119. indicatorStyle: function indicatorStyle() {
  120. return {
  121. backgroundColor: this.indicatorColor
  122. };
  123. },
  124. minOffset: function minOffset() {
  125. return (this.vertical ? this.rect.height : this.rect.width) - this.size * this.count;
  126. }
  127. },
  128. mounted: function mounted() {
  129. this.bindTouchEvent(this.$refs.track);
  130. },
  131. methods: {
  132. // initialize swipe position
  133. initialize: function initialize(active) {
  134. if (active === void 0) {
  135. active = +this.initialSwipe;
  136. }
  137. if (!this.$el || isHidden(this.$el)) {
  138. return;
  139. }
  140. clearTimeout(this.timer);
  141. var rect = {
  142. width: this.$el.offsetWidth,
  143. height: this.$el.offsetHeight
  144. };
  145. this.rect = rect;
  146. this.swiping = true;
  147. this.active = active;
  148. this.computedWidth = +this.width || rect.width;
  149. this.computedHeight = +this.height || rect.height;
  150. this.offset = this.getTargetOffset(active);
  151. this.children.forEach(function (swipe) {
  152. swipe.offset = 0;
  153. });
  154. this.autoPlay();
  155. },
  156. // @exposed-api
  157. resize: function resize() {
  158. this.initialize(this.activeIndicator);
  159. },
  160. onVisibilityChange: function onVisibilityChange() {
  161. if (document.hidden) {
  162. this.clear();
  163. } else {
  164. this.autoPlay();
  165. }
  166. },
  167. onTouchStart: function onTouchStart(event) {
  168. if (!this.touchable) return;
  169. this.clear();
  170. this.touchStartTime = Date.now();
  171. this.touchStart(event);
  172. this.correctPosition();
  173. },
  174. onTouchMove: function onTouchMove(event) {
  175. if (!this.touchable || !this.swiping) return;
  176. this.touchMove(event);
  177. if (this.isCorrectDirection) {
  178. preventDefault(event, this.stopPropagation);
  179. this.move({
  180. offset: this.delta
  181. });
  182. }
  183. },
  184. onTouchEnd: function onTouchEnd() {
  185. if (!this.touchable || !this.swiping) return;
  186. var size = this.size,
  187. delta = this.delta;
  188. var duration = Date.now() - this.touchStartTime;
  189. var speed = delta / duration;
  190. var shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(delta) > size / 2;
  191. if (shouldSwipe && this.isCorrectDirection) {
  192. var offset = this.vertical ? this.offsetY : this.offsetX;
  193. var pace = 0;
  194. if (this.loop) {
  195. pace = offset > 0 ? delta > 0 ? -1 : 1 : 0;
  196. } else {
  197. pace = -Math[delta > 0 ? 'ceil' : 'floor'](delta / size);
  198. }
  199. this.move({
  200. pace: pace,
  201. emitChange: true
  202. });
  203. } else if (delta) {
  204. this.move({
  205. pace: 0
  206. });
  207. }
  208. this.swiping = false;
  209. this.autoPlay();
  210. },
  211. getTargetActive: function getTargetActive(pace) {
  212. var active = this.active,
  213. count = this.count,
  214. maxCount = this.maxCount;
  215. if (pace) {
  216. if (this.loop) {
  217. return range(active + pace, -1, count);
  218. }
  219. return range(active + pace, 0, maxCount);
  220. }
  221. return active;
  222. },
  223. getTargetOffset: function getTargetOffset(targetActive, offset) {
  224. if (offset === void 0) {
  225. offset = 0;
  226. }
  227. var currentPosition = targetActive * this.size;
  228. if (!this.loop) {
  229. currentPosition = Math.min(currentPosition, -this.minOffset);
  230. }
  231. var targetOffset = offset - currentPosition;
  232. if (!this.loop) {
  233. targetOffset = range(targetOffset, this.minOffset, 0);
  234. }
  235. return targetOffset;
  236. },
  237. move: function move(_ref) {
  238. var _ref$pace = _ref.pace,
  239. pace = _ref$pace === void 0 ? 0 : _ref$pace,
  240. _ref$offset = _ref.offset,
  241. offset = _ref$offset === void 0 ? 0 : _ref$offset,
  242. emitChange = _ref.emitChange;
  243. var loop = this.loop,
  244. count = this.count,
  245. active = this.active,
  246. children = this.children,
  247. trackSize = this.trackSize,
  248. minOffset = this.minOffset;
  249. if (count <= 1) {
  250. return;
  251. }
  252. var targetActive = this.getTargetActive(pace);
  253. var targetOffset = this.getTargetOffset(targetActive, offset); // auto move first and last swipe in loop mode
  254. if (loop) {
  255. if (children[0] && targetOffset !== minOffset) {
  256. var outRightBound = targetOffset < minOffset;
  257. children[0].offset = outRightBound ? trackSize : 0;
  258. }
  259. if (children[count - 1] && targetOffset !== 0) {
  260. var outLeftBound = targetOffset > 0;
  261. children[count - 1].offset = outLeftBound ? -trackSize : 0;
  262. }
  263. }
  264. this.active = targetActive;
  265. this.offset = targetOffset;
  266. if (emitChange && targetActive !== active) {
  267. this.$emit('change', this.activeIndicator);
  268. }
  269. },
  270. // @exposed-api
  271. prev: function prev() {
  272. var _this = this;
  273. this.correctPosition();
  274. this.resetTouchStatus();
  275. doubleRaf(function () {
  276. _this.swiping = false;
  277. _this.move({
  278. pace: -1,
  279. emitChange: true
  280. });
  281. });
  282. },
  283. // @exposed-api
  284. next: function next() {
  285. var _this2 = this;
  286. this.correctPosition();
  287. this.resetTouchStatus();
  288. doubleRaf(function () {
  289. _this2.swiping = false;
  290. _this2.move({
  291. pace: 1,
  292. emitChange: true
  293. });
  294. });
  295. },
  296. // @exposed-api
  297. swipeTo: function swipeTo(index, options) {
  298. var _this3 = this;
  299. if (options === void 0) {
  300. options = {};
  301. }
  302. this.correctPosition();
  303. this.resetTouchStatus();
  304. doubleRaf(function () {
  305. var targetIndex;
  306. if (_this3.loop && index === _this3.count) {
  307. targetIndex = _this3.active === 0 ? 0 : index;
  308. } else {
  309. targetIndex = index % _this3.count;
  310. }
  311. if (options.immediate) {
  312. doubleRaf(function () {
  313. _this3.swiping = false;
  314. });
  315. } else {
  316. _this3.swiping = false;
  317. }
  318. _this3.move({
  319. pace: targetIndex - _this3.active,
  320. emitChange: true
  321. });
  322. });
  323. },
  324. correctPosition: function correctPosition() {
  325. this.swiping = true;
  326. if (this.active <= -1) {
  327. this.move({
  328. pace: this.count
  329. });
  330. }
  331. if (this.active >= this.count) {
  332. this.move({
  333. pace: -this.count
  334. });
  335. }
  336. },
  337. clear: function clear() {
  338. clearTimeout(this.timer);
  339. },
  340. autoPlay: function autoPlay() {
  341. var _this4 = this;
  342. var autoplay = this.autoplay;
  343. if (autoplay > 0 && this.count > 1) {
  344. this.clear();
  345. this.timer = setTimeout(function () {
  346. _this4.next();
  347. _this4.autoPlay();
  348. }, autoplay);
  349. }
  350. },
  351. genIndicator: function genIndicator() {
  352. var _this5 = this;
  353. var h = this.$createElement;
  354. var count = this.count,
  355. activeIndicator = this.activeIndicator;
  356. var slot = this.slots('indicator');
  357. if (slot) {
  358. return slot;
  359. }
  360. if (this.showIndicators && count > 1) {
  361. return h("div", {
  362. "class": bem('indicators', {
  363. vertical: this.vertical
  364. })
  365. }, [Array.apply(void 0, Array(count)).map(function (empty, index) {
  366. return h("i", {
  367. "class": bem('indicator', {
  368. active: index === activeIndicator
  369. }),
  370. "style": index === activeIndicator ? _this5.indicatorStyle : null
  371. });
  372. })]);
  373. }
  374. }
  375. },
  376. render: function render() {
  377. var h = arguments[0];
  378. return h("div", {
  379. "class": bem()
  380. }, [h("div", {
  381. "ref": "track",
  382. "style": this.trackStyle,
  383. "class": bem('track', {
  384. vertical: this.vertical
  385. })
  386. }, [this.slots()]), this.genIndicator()]);
  387. }
  388. });