Swipe.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. var __defProp = Object.defineProperty;
  2. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  3. var __getOwnPropNames = Object.getOwnPropertyNames;
  4. var __hasOwnProp = Object.prototype.hasOwnProperty;
  5. var __export = (target, all) => {
  6. for (var name2 in all)
  7. __defProp(target, name2, { get: all[name2], enumerable: true });
  8. };
  9. var __copyProps = (to, from, except, desc) => {
  10. if (from && typeof from === "object" || typeof from === "function") {
  11. for (let key of __getOwnPropNames(from))
  12. if (!__hasOwnProp.call(to, key) && key !== except)
  13. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  14. }
  15. return to;
  16. };
  17. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  18. var stdin_exports = {};
  19. __export(stdin_exports, {
  20. SWIPE_KEY: () => SWIPE_KEY,
  21. default: () => stdin_default,
  22. swipeProps: () => swipeProps
  23. });
  24. module.exports = __toCommonJS(stdin_exports);
  25. var import_vue = require("vue");
  26. var import_utils = require("../utils");
  27. var import_use = require("@vant/use");
  28. var import_use_touch = require("../composables/use-touch");
  29. var import_use_expose = require("../composables/use-expose");
  30. var import_on_popup_reopen = require("../composables/on-popup-reopen");
  31. const [name, bem] = (0, import_utils.createNamespace)("swipe");
  32. const swipeProps = {
  33. loop: import_utils.truthProp,
  34. width: import_utils.numericProp,
  35. height: import_utils.numericProp,
  36. vertical: Boolean,
  37. autoplay: (0, import_utils.makeNumericProp)(0),
  38. duration: (0, import_utils.makeNumericProp)(500),
  39. touchable: import_utils.truthProp,
  40. lazyRender: Boolean,
  41. initialSwipe: (0, import_utils.makeNumericProp)(0),
  42. indicatorColor: String,
  43. showIndicators: import_utils.truthProp,
  44. stopPropagation: import_utils.truthProp
  45. };
  46. const SWIPE_KEY = Symbol(name);
  47. var stdin_default = (0, import_vue.defineComponent)({
  48. name,
  49. props: swipeProps,
  50. emits: ["change", "dragStart", "dragEnd"],
  51. setup(props, {
  52. emit,
  53. slots
  54. }) {
  55. const root = (0, import_vue.ref)();
  56. const track = (0, import_vue.ref)();
  57. const state = (0, import_vue.reactive)({
  58. rect: null,
  59. width: 0,
  60. height: 0,
  61. offset: 0,
  62. active: 0,
  63. swiping: false
  64. });
  65. let dragging = false;
  66. const touch = (0, import_use_touch.useTouch)();
  67. const {
  68. children,
  69. linkChildren
  70. } = (0, import_use.useChildren)(SWIPE_KEY);
  71. const count = (0, import_vue.computed)(() => children.length);
  72. const size = (0, import_vue.computed)(() => state[props.vertical ? "height" : "width"]);
  73. const delta = (0, import_vue.computed)(() => props.vertical ? touch.deltaY.value : touch.deltaX.value);
  74. const minOffset = (0, import_vue.computed)(() => {
  75. if (state.rect) {
  76. const base = props.vertical ? state.rect.height : state.rect.width;
  77. return base - size.value * count.value;
  78. }
  79. return 0;
  80. });
  81. const maxCount = (0, import_vue.computed)(() => size.value ? Math.ceil(Math.abs(minOffset.value) / size.value) : count.value);
  82. const trackSize = (0, import_vue.computed)(() => count.value * size.value);
  83. const activeIndicator = (0, import_vue.computed)(() => (state.active + count.value) % count.value);
  84. const isCorrectDirection = (0, import_vue.computed)(() => {
  85. const expect = props.vertical ? "vertical" : "horizontal";
  86. return touch.direction.value === expect;
  87. });
  88. const trackStyle = (0, import_vue.computed)(() => {
  89. const style = {
  90. transitionDuration: `${state.swiping ? 0 : props.duration}ms`,
  91. transform: `translate${props.vertical ? "Y" : "X"}(${+state.offset.toFixed(2)}px)`
  92. };
  93. if (size.value) {
  94. const mainAxis = props.vertical ? "height" : "width";
  95. const crossAxis = props.vertical ? "width" : "height";
  96. style[mainAxis] = `${trackSize.value}px`;
  97. style[crossAxis] = props[crossAxis] ? `${props[crossAxis]}px` : "";
  98. }
  99. return style;
  100. });
  101. const getTargetActive = (pace) => {
  102. const {
  103. active
  104. } = state;
  105. if (pace) {
  106. if (props.loop) {
  107. return (0, import_utils.clamp)(active + pace, -1, count.value);
  108. }
  109. return (0, import_utils.clamp)(active + pace, 0, maxCount.value);
  110. }
  111. return active;
  112. };
  113. const getTargetOffset = (targetActive, offset = 0) => {
  114. let currentPosition = targetActive * size.value;
  115. if (!props.loop) {
  116. currentPosition = Math.min(currentPosition, -minOffset.value);
  117. }
  118. let targetOffset = offset - currentPosition;
  119. if (!props.loop) {
  120. targetOffset = (0, import_utils.clamp)(targetOffset, minOffset.value, 0);
  121. }
  122. return targetOffset;
  123. };
  124. const move = ({
  125. pace = 0,
  126. offset = 0,
  127. emitChange
  128. }) => {
  129. if (count.value <= 1) {
  130. return;
  131. }
  132. const {
  133. active
  134. } = state;
  135. const targetActive = getTargetActive(pace);
  136. const targetOffset = getTargetOffset(targetActive, offset);
  137. if (props.loop) {
  138. if (children[0] && targetOffset !== minOffset.value) {
  139. const outRightBound = targetOffset < minOffset.value;
  140. children[0].setOffset(outRightBound ? trackSize.value : 0);
  141. }
  142. if (children[count.value - 1] && targetOffset !== 0) {
  143. const outLeftBound = targetOffset > 0;
  144. children[count.value - 1].setOffset(outLeftBound ? -trackSize.value : 0);
  145. }
  146. }
  147. state.active = targetActive;
  148. state.offset = targetOffset;
  149. if (emitChange && targetActive !== active) {
  150. emit("change", activeIndicator.value);
  151. }
  152. };
  153. const correctPosition = () => {
  154. state.swiping = true;
  155. if (state.active <= -1) {
  156. move({
  157. pace: count.value
  158. });
  159. } else if (state.active >= count.value) {
  160. move({
  161. pace: -count.value
  162. });
  163. }
  164. };
  165. const prev = () => {
  166. correctPosition();
  167. touch.reset();
  168. (0, import_use.doubleRaf)(() => {
  169. state.swiping = false;
  170. move({
  171. pace: -1,
  172. emitChange: true
  173. });
  174. });
  175. };
  176. const next = () => {
  177. correctPosition();
  178. touch.reset();
  179. (0, import_use.doubleRaf)(() => {
  180. state.swiping = false;
  181. move({
  182. pace: 1,
  183. emitChange: true
  184. });
  185. });
  186. };
  187. let autoplayTimer;
  188. const stopAutoplay = () => clearTimeout(autoplayTimer);
  189. const autoplay = () => {
  190. stopAutoplay();
  191. if (+props.autoplay > 0 && count.value > 1) {
  192. autoplayTimer = setTimeout(() => {
  193. next();
  194. autoplay();
  195. }, +props.autoplay);
  196. }
  197. };
  198. const initialize = (active = +props.initialSwipe) => {
  199. if (!root.value) {
  200. return;
  201. }
  202. const cb = () => {
  203. var _a, _b;
  204. if (!(0, import_utils.isHidden)(root)) {
  205. const rect = {
  206. width: root.value.offsetWidth,
  207. height: root.value.offsetHeight
  208. };
  209. state.rect = rect;
  210. state.width = +((_a = props.width) != null ? _a : rect.width);
  211. state.height = +((_b = props.height) != null ? _b : rect.height);
  212. }
  213. if (count.value) {
  214. active = Math.min(count.value - 1, active);
  215. if (active === -1) {
  216. active = count.value - 1;
  217. }
  218. }
  219. state.active = active;
  220. state.swiping = true;
  221. state.offset = getTargetOffset(active);
  222. children.forEach((swipe) => {
  223. swipe.setOffset(0);
  224. });
  225. autoplay();
  226. };
  227. if ((0, import_utils.isHidden)(root)) {
  228. (0, import_vue.nextTick)().then(cb);
  229. } else {
  230. cb();
  231. }
  232. };
  233. const resize = () => initialize(state.active);
  234. let touchStartTime;
  235. const onTouchStart = (event) => {
  236. if (!props.touchable || // avoid resetting position on multi-finger touch
  237. event.touches.length > 1) return;
  238. touch.start(event);
  239. dragging = false;
  240. touchStartTime = Date.now();
  241. stopAutoplay();
  242. correctPosition();
  243. };
  244. const onTouchMove = (event) => {
  245. if (props.touchable && state.swiping) {
  246. touch.move(event);
  247. if (isCorrectDirection.value) {
  248. const isEdgeTouch = !props.loop && (state.active === 0 && delta.value > 0 || state.active === count.value - 1 && delta.value < 0);
  249. if (!isEdgeTouch) {
  250. (0, import_utils.preventDefault)(event, props.stopPropagation);
  251. move({
  252. offset: delta.value
  253. });
  254. if (!dragging) {
  255. emit("dragStart", {
  256. index: activeIndicator.value
  257. });
  258. dragging = true;
  259. }
  260. }
  261. }
  262. }
  263. };
  264. const onTouchEnd = () => {
  265. if (!props.touchable || !state.swiping) {
  266. return;
  267. }
  268. const duration = Date.now() - touchStartTime;
  269. const speed = delta.value / duration;
  270. const shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(delta.value) > size.value / 2;
  271. if (shouldSwipe && isCorrectDirection.value) {
  272. const offset = props.vertical ? touch.offsetY.value : touch.offsetX.value;
  273. let pace = 0;
  274. if (props.loop) {
  275. pace = offset > 0 ? delta.value > 0 ? -1 : 1 : 0;
  276. } else {
  277. pace = -Math[delta.value > 0 ? "ceil" : "floor"](delta.value / size.value);
  278. }
  279. move({
  280. pace,
  281. emitChange: true
  282. });
  283. } else if (delta.value) {
  284. move({
  285. pace: 0
  286. });
  287. }
  288. dragging = false;
  289. state.swiping = false;
  290. emit("dragEnd", {
  291. index: activeIndicator.value
  292. });
  293. autoplay();
  294. };
  295. const swipeTo = (index, options = {}) => {
  296. correctPosition();
  297. touch.reset();
  298. (0, import_use.doubleRaf)(() => {
  299. let targetIndex;
  300. if (props.loop && index === count.value) {
  301. targetIndex = state.active === 0 ? 0 : index;
  302. } else {
  303. targetIndex = index % count.value;
  304. }
  305. if (options.immediate) {
  306. (0, import_use.doubleRaf)(() => {
  307. state.swiping = false;
  308. });
  309. } else {
  310. state.swiping = false;
  311. }
  312. move({
  313. pace: targetIndex - state.active,
  314. emitChange: true
  315. });
  316. });
  317. };
  318. const renderDot = (_, index) => {
  319. const active = index === activeIndicator.value;
  320. const style = active ? {
  321. backgroundColor: props.indicatorColor
  322. } : void 0;
  323. return (0, import_vue.createVNode)("i", {
  324. "style": style,
  325. "class": bem("indicator", {
  326. active
  327. })
  328. }, null);
  329. };
  330. const renderIndicator = () => {
  331. if (slots.indicator) {
  332. return slots.indicator({
  333. active: activeIndicator.value,
  334. total: count.value
  335. });
  336. }
  337. if (props.showIndicators && count.value > 1) {
  338. return (0, import_vue.createVNode)("div", {
  339. "class": bem("indicators", {
  340. vertical: props.vertical
  341. })
  342. }, [Array(count.value).fill("").map(renderDot)]);
  343. }
  344. };
  345. (0, import_use_expose.useExpose)({
  346. prev,
  347. next,
  348. state,
  349. resize,
  350. swipeTo
  351. });
  352. linkChildren({
  353. size,
  354. props,
  355. count,
  356. activeIndicator
  357. });
  358. (0, import_vue.watch)(() => props.initialSwipe, (value) => initialize(+value));
  359. (0, import_vue.watch)(count, () => initialize(state.active));
  360. (0, import_vue.watch)(() => props.autoplay, autoplay);
  361. (0, import_vue.watch)([import_utils.windowWidth, import_utils.windowHeight, () => props.width, () => props.height], resize);
  362. (0, import_vue.watch)((0, import_use.usePageVisibility)(), (visible) => {
  363. if (visible === "visible") {
  364. autoplay();
  365. } else {
  366. stopAutoplay();
  367. }
  368. });
  369. (0, import_vue.onMounted)(initialize);
  370. (0, import_vue.onActivated)(() => initialize(state.active));
  371. (0, import_on_popup_reopen.onPopupReopen)(() => initialize(state.active));
  372. (0, import_vue.onDeactivated)(stopAutoplay);
  373. (0, import_vue.onBeforeUnmount)(stopAutoplay);
  374. (0, import_use.useEventListener)("touchmove", onTouchMove, {
  375. target: track
  376. });
  377. return () => {
  378. var _a;
  379. return (0, import_vue.createVNode)("div", {
  380. "ref": root,
  381. "class": bem()
  382. }, [(0, import_vue.createVNode)("div", {
  383. "ref": track,
  384. "style": trackStyle.value,
  385. "class": bem("track", {
  386. vertical: props.vertical
  387. }),
  388. "onTouchstartPassive": onTouchStart,
  389. "onTouchend": onTouchEnd,
  390. "onTouchcancel": onTouchEnd
  391. }, [(_a = slots.default) == null ? void 0 : _a.call(slots)]), renderIndicator()]);
  392. };
  393. }
  394. });