ImagePreviewItem.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 name in all)
  7. __defProp(target, name, { get: all[name], 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. default: () => stdin_default
  21. });
  22. module.exports = __toCommonJS(stdin_exports);
  23. var import_vue = require("vue");
  24. var import_utils = require("../utils");
  25. var import_use_expose = require("../composables/use-expose");
  26. var import_use_touch = require("../composables/use-touch");
  27. var import_use = require("@vant/use");
  28. var import_image = require("../image");
  29. var import_loading = require("../loading");
  30. var import_swipe_item = require("../swipe-item");
  31. const getDistance = (touches) => Math.sqrt((touches[0].clientX - touches[1].clientX) ** 2 + (touches[0].clientY - touches[1].clientY) ** 2);
  32. const getCenter = (touches) => ({
  33. x: (touches[0].clientX + touches[1].clientX) / 2,
  34. y: (touches[0].clientY + touches[1].clientY) / 2
  35. });
  36. const bem = (0, import_utils.createNamespace)("image-preview")[1];
  37. const longImageRatio = 2.6;
  38. const imagePreviewItemProps = {
  39. src: String,
  40. show: Boolean,
  41. active: Number,
  42. minZoom: (0, import_utils.makeRequiredProp)(import_utils.numericProp),
  43. maxZoom: (0, import_utils.makeRequiredProp)(import_utils.numericProp),
  44. rootWidth: (0, import_utils.makeRequiredProp)(Number),
  45. rootHeight: (0, import_utils.makeRequiredProp)(Number),
  46. disableZoom: Boolean,
  47. doubleScale: Boolean,
  48. closeOnClickImage: Boolean,
  49. closeOnClickOverlay: Boolean,
  50. vertical: Boolean
  51. };
  52. var stdin_default = (0, import_vue.defineComponent)({
  53. props: imagePreviewItemProps,
  54. emits: ["scale", "close", "longPress"],
  55. setup(props, {
  56. emit,
  57. slots
  58. }) {
  59. const state = (0, import_vue.reactive)({
  60. scale: 1,
  61. moveX: 0,
  62. moveY: 0,
  63. moving: false,
  64. zooming: false,
  65. initializing: false,
  66. imageRatio: 0
  67. });
  68. const touch = (0, import_use_touch.useTouch)();
  69. const imageRef = (0, import_vue.ref)();
  70. const swipeItem = (0, import_vue.ref)();
  71. const vertical = (0, import_vue.ref)(false);
  72. const isLongImage = (0, import_vue.ref)(false);
  73. let initialMoveY = 0;
  74. const imageStyle = (0, import_vue.computed)(() => {
  75. const {
  76. scale,
  77. moveX,
  78. moveY,
  79. moving,
  80. zooming,
  81. initializing
  82. } = state;
  83. const style = {
  84. transitionDuration: zooming || moving || initializing ? "0s" : ".3s"
  85. };
  86. if (scale !== 1 || isLongImage.value) {
  87. style.transform = `matrix(${scale}, 0, 0, ${scale}, ${moveX}, ${moveY})`;
  88. }
  89. return style;
  90. });
  91. const maxMoveX = (0, import_vue.computed)(() => {
  92. if (state.imageRatio) {
  93. const {
  94. rootWidth,
  95. rootHeight
  96. } = props;
  97. const displayWidth = vertical.value ? rootHeight / state.imageRatio : rootWidth;
  98. return Math.max(0, (state.scale * displayWidth - rootWidth) / 2);
  99. }
  100. return 0;
  101. });
  102. const maxMoveY = (0, import_vue.computed)(() => {
  103. if (state.imageRatio) {
  104. const {
  105. rootWidth,
  106. rootHeight
  107. } = props;
  108. const displayHeight = vertical.value ? rootHeight : rootWidth * state.imageRatio;
  109. return Math.max(0, (state.scale * displayHeight - rootHeight) / 2);
  110. }
  111. return 0;
  112. });
  113. const setScale = (scale, center) => {
  114. var _a;
  115. scale = (0, import_utils.clamp)(scale, +props.minZoom, +props.maxZoom + 1);
  116. if (scale !== state.scale) {
  117. const ratio = scale / state.scale;
  118. state.scale = scale;
  119. if (center) {
  120. const imageRect = (0, import_use.useRect)((_a = imageRef.value) == null ? void 0 : _a.$el);
  121. const origin = {
  122. x: imageRect.width * 0.5,
  123. y: imageRect.height * 0.5
  124. };
  125. const moveX = state.moveX - (center.x - imageRect.left - origin.x) * (ratio - 1);
  126. const moveY = state.moveY - (center.y - imageRect.top - origin.y) * (ratio - 1);
  127. state.moveX = (0, import_utils.clamp)(moveX, -maxMoveX.value, maxMoveX.value);
  128. state.moveY = (0, import_utils.clamp)(moveY, -maxMoveY.value, maxMoveY.value);
  129. } else {
  130. state.moveX = 0;
  131. state.moveY = isLongImage.value ? initialMoveY : 0;
  132. }
  133. emit("scale", {
  134. scale,
  135. index: props.active
  136. });
  137. }
  138. };
  139. const resetScale = () => {
  140. setScale(1);
  141. };
  142. const toggleScale = () => {
  143. const scale = state.scale > 1 ? 1 : 2;
  144. setScale(scale, scale === 2 || isLongImage.value ? {
  145. x: touch.startX.value,
  146. y: touch.startY.value
  147. } : void 0);
  148. };
  149. let fingerNum;
  150. let startMoveX;
  151. let startMoveY;
  152. let startScale;
  153. let startDistance;
  154. let lastCenter;
  155. let doubleTapTimer;
  156. let touchStartTime;
  157. let isImageMoved = false;
  158. const onTouchStart = (event) => {
  159. const {
  160. touches
  161. } = event;
  162. fingerNum = touches.length;
  163. if (fingerNum === 2 && props.disableZoom) {
  164. return;
  165. }
  166. const {
  167. offsetX
  168. } = touch;
  169. touch.start(event);
  170. startMoveX = state.moveX;
  171. startMoveY = state.moveY;
  172. touchStartTime = Date.now();
  173. isImageMoved = false;
  174. state.moving = fingerNum === 1 && (state.scale !== 1 || isLongImage.value);
  175. state.zooming = fingerNum === 2 && !offsetX.value;
  176. if (state.zooming) {
  177. startScale = state.scale;
  178. startDistance = getDistance(touches);
  179. }
  180. };
  181. const onTouchMove = (event) => {
  182. const {
  183. touches
  184. } = event;
  185. touch.move(event);
  186. if (state.moving) {
  187. const {
  188. deltaX,
  189. deltaY
  190. } = touch;
  191. const moveX = deltaX.value + startMoveX;
  192. const moveY = deltaY.value + startMoveY;
  193. if ((props.vertical ? touch.isVertical() && Math.abs(moveY) > maxMoveY.value : touch.isHorizontal() && Math.abs(moveX) > maxMoveX.value) && !isImageMoved) {
  194. state.moving = false;
  195. return;
  196. }
  197. isImageMoved = true;
  198. (0, import_utils.preventDefault)(event, true);
  199. state.moveX = (0, import_utils.clamp)(moveX, -maxMoveX.value, maxMoveX.value);
  200. state.moveY = (0, import_utils.clamp)(moveY, -maxMoveY.value, maxMoveY.value);
  201. }
  202. if (state.zooming) {
  203. (0, import_utils.preventDefault)(event, true);
  204. if (touches.length === 2) {
  205. const distance = getDistance(touches);
  206. const scale = startScale * distance / startDistance;
  207. lastCenter = getCenter(touches);
  208. setScale(scale, lastCenter);
  209. }
  210. }
  211. };
  212. const checkClose = (event) => {
  213. var _a;
  214. const swipeItemEl = (_a = swipeItem.value) == null ? void 0 : _a.$el;
  215. if (!swipeItemEl) return;
  216. const imageEl = swipeItemEl.firstElementChild;
  217. const isClickOverlay = event.target === swipeItemEl;
  218. const isClickImage = imageEl == null ? void 0 : imageEl.contains(event.target);
  219. if (!props.closeOnClickImage && isClickImage) return;
  220. if (!props.closeOnClickOverlay && isClickOverlay) return;
  221. emit("close");
  222. };
  223. const checkTap = (event) => {
  224. if (fingerNum > 1) {
  225. return;
  226. }
  227. const deltaTime = Date.now() - touchStartTime;
  228. const TAP_TIME = 250;
  229. if (touch.isTap.value) {
  230. if (deltaTime < TAP_TIME) {
  231. if (props.doubleScale) {
  232. if (doubleTapTimer) {
  233. clearTimeout(doubleTapTimer);
  234. doubleTapTimer = null;
  235. toggleScale();
  236. } else {
  237. doubleTapTimer = setTimeout(() => {
  238. checkClose(event);
  239. doubleTapTimer = null;
  240. }, TAP_TIME);
  241. }
  242. } else {
  243. checkClose(event);
  244. }
  245. } else if (deltaTime > import_utils.LONG_PRESS_START_TIME) {
  246. emit("longPress");
  247. }
  248. }
  249. };
  250. const onTouchEnd = (event) => {
  251. let stopPropagation = false;
  252. if (state.moving || state.zooming) {
  253. stopPropagation = true;
  254. if (state.moving && startMoveX === state.moveX && startMoveY === state.moveY) {
  255. stopPropagation = false;
  256. }
  257. if (!event.touches.length) {
  258. if (state.zooming) {
  259. state.moveX = (0, import_utils.clamp)(state.moveX, -maxMoveX.value, maxMoveX.value);
  260. state.moveY = (0, import_utils.clamp)(state.moveY, -maxMoveY.value, maxMoveY.value);
  261. state.zooming = false;
  262. }
  263. state.moving = false;
  264. startMoveX = 0;
  265. startMoveY = 0;
  266. startScale = 1;
  267. if (state.scale < 1) {
  268. resetScale();
  269. }
  270. const maxZoom = +props.maxZoom;
  271. if (state.scale > maxZoom) {
  272. setScale(maxZoom, lastCenter);
  273. }
  274. }
  275. }
  276. (0, import_utils.preventDefault)(event, stopPropagation);
  277. checkTap(event);
  278. touch.reset();
  279. };
  280. const resize = () => {
  281. const {
  282. rootWidth,
  283. rootHeight
  284. } = props;
  285. const rootRatio = rootHeight / rootWidth;
  286. const {
  287. imageRatio
  288. } = state;
  289. vertical.value = state.imageRatio > rootRatio && imageRatio < longImageRatio;
  290. isLongImage.value = state.imageRatio > rootRatio && imageRatio >= longImageRatio;
  291. if (isLongImage.value) {
  292. initialMoveY = (imageRatio * rootWidth - rootHeight) / 2;
  293. state.moveY = initialMoveY;
  294. state.initializing = true;
  295. (0, import_use.raf)(() => {
  296. state.initializing = false;
  297. });
  298. }
  299. resetScale();
  300. };
  301. const onLoad = (event) => {
  302. const {
  303. naturalWidth,
  304. naturalHeight
  305. } = event.target;
  306. state.imageRatio = naturalHeight / naturalWidth;
  307. resize();
  308. };
  309. (0, import_vue.watch)(() => props.active, resetScale);
  310. (0, import_vue.watch)(() => props.show, (value) => {
  311. if (!value) {
  312. resetScale();
  313. }
  314. });
  315. (0, import_vue.watch)(() => [props.rootWidth, props.rootHeight], resize);
  316. (0, import_use.useEventListener)("touchmove", onTouchMove, {
  317. target: (0, import_vue.computed)(() => {
  318. var _a;
  319. return (_a = swipeItem.value) == null ? void 0 : _a.$el;
  320. })
  321. });
  322. (0, import_use_expose.useExpose)({
  323. resetScale
  324. });
  325. return () => {
  326. const imageSlots = {
  327. loading: () => (0, import_vue.createVNode)(import_loading.Loading, {
  328. "type": "spinner"
  329. }, null)
  330. };
  331. return (0, import_vue.createVNode)(import_swipe_item.SwipeItem, {
  332. "ref": swipeItem,
  333. "class": bem("swipe-item"),
  334. "onTouchstartPassive": onTouchStart,
  335. "onTouchend": onTouchEnd,
  336. "onTouchcancel": onTouchEnd
  337. }, {
  338. default: () => [slots.image ? (0, import_vue.createVNode)("div", {
  339. "class": bem("image-wrap")
  340. }, [slots.image({
  341. src: props.src,
  342. onLoad,
  343. style: imageStyle.value
  344. })]) : (0, import_vue.createVNode)(import_image.Image, {
  345. "ref": imageRef,
  346. "src": props.src,
  347. "fit": "contain",
  348. "class": bem("image", {
  349. vertical: vertical.value
  350. }),
  351. "style": imageStyle.value,
  352. "onLoad": onLoad
  353. }, imageSlots)]
  354. });
  355. };
  356. }
  357. });