UNPKG

react-photo-view-fix

Version:

一款精致的 React 的图片预览组件

1,357 lines (1,284 loc) 72.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = _interopDefault(require('react')); var classNames = _interopDefault(require('classnames')); var reactDom = require('react-dom'); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } var PhotoContext = React.createContext({ onShow: function () { }, addItem: function () { }, removeItem: function () { }, }); function Spinner() { return (React.createElement("div", { className: "PhotoView__Spinner" }, React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 32 32", width: "36", height: "36", fill: "white" }, React.createElement("path", { opacity: ".25", d: "M16 0 A16 16 0 0 0 16 32 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 16 28 A12 12 0 0 1 16 4" }), React.createElement("path", { d: "M16 0 A16 16 0 0 1 32 16 L28 16 A12 12 0 0 0 16 4z" })))); } /** * 获取图片合适的大小 */ function getSuitableImageSize(naturalWidth, naturalHeight, rotate) { var _a; var width; var height; var y = 0; var innerWidth = window.innerWidth, innerHeight = window.innerHeight; var isVertical = rotate % 180 !== 0; // 若图片不是水平则调换宽高 if (isVertical) { _a = [innerWidth, innerHeight], innerHeight = _a[0], innerWidth = _a[1]; } var autoWidth = (naturalWidth / naturalHeight) * innerHeight; var autoHeight = (naturalHeight / naturalWidth) * innerWidth; if (naturalWidth < innerWidth && naturalHeight < innerHeight) { width = naturalWidth; height = naturalHeight; } else if (naturalWidth < innerWidth && naturalHeight >= innerHeight) { width = autoWidth; height = innerHeight; } else if (naturalWidth >= innerWidth && naturalHeight < innerHeight) { width = innerWidth; height = autoHeight; } else if (naturalWidth / naturalHeight > innerWidth / innerHeight) { width = innerWidth; height = autoHeight; } // 长图模式 else if (naturalHeight / naturalWidth >= 3 && !isVertical) { width = innerWidth; height = autoHeight; // 默认定位到顶部区域 y = (height - innerHeight) / 2; } else { width = autoWidth; height = innerHeight; } return { width: Math.floor(width), height: Math.floor(height), x: 0, y: y, scale: 1, }; } function useMountedState() { var mountedRef = React.useRef(false); React.useEffect(function () { mountedRef.current = true; return function () { mountedRef.current = false; }; }); return React.useCallback(function () { return mountedRef.current; }, []); } var Photo = function (props) { var src = props.src, intro = props.intro, loaded = props.loaded, broken = props.broken, width = props.width, height = props.height, rotate = props.rotate, className = props.className, onImageLoad = props.onImageLoad, loadingElement = props.loadingElement, brokenElement = props.brokenElement, restProps = __rest(props, ["src", "intro", "loaded", "broken", "width", "height", "rotate", "className", "onImageLoad", "loadingElement", "brokenElement"]); var isMounted = useMountedState(); function handleImageLoaded(e) { var _a = e.target, naturalWidth = _a.naturalWidth, naturalHeight = _a.naturalHeight; if (isMounted()) { onImageLoad(__assign({ loaded: true, naturalWidth: naturalWidth, naturalHeight: naturalHeight }, getSuitableImageSize(naturalWidth, naturalHeight, rotate))); } } function handleImageBroken() { if (isMounted()) { onImageLoad({ broken: true, }); } } React.useEffect(function () { var currPhoto = new Image(); currPhoto.onload = handleImageLoaded; currPhoto.onerror = handleImageBroken; currPhoto.src = src; }, []); if (src && !broken) { if (loaded) { return (React.createElement("img", __assign({ className: classNames('PhotoView__Photo', className), src: src, width: width, height: height, alt: "" }, restProps))); } return loadingElement || React.createElement(Spinner, null); } if (brokenElement) { if (typeof brokenElement === 'function') { return brokenElement({ src: src, intro: intro, }); } return brokenElement; } return null; }; Photo.displayName = 'Photo'; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; /** * lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` * Copyright jQuery Foundation and other contributors <https://jquery.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ /** Used as the `TypeError` message for "Functions" methods. */ var FUNC_ERROR_TEXT = 'Expected a function'; /** Used as references for various `Number` constants. */ var NAN = 0 / 0; /** `Object#toString` result references. */ var symbolTag = '[object Symbol]'; /** Used to match leading and trailing whitespace. */ var reTrim = /^\s+|\s+$/g; /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; /** Used to detect binary string values. */ var reIsBinary = /^0b[01]+$/i; /** Used to detect octal string values. */ var reIsOctal = /^0o[0-7]+$/i; /** Built-in method references without a dependency on `root`. */ var freeParseInt = parseInt; /** Detect free variable `global` from Node.js. */ var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; /** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self; /** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')(); /** Used for built-in method references. */ var objectProto = Object.prototype; /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var objectToString = objectProto.toString; /* Built-in method references for those with the same name as other `lodash` methods. */ var nativeMax = Math.max, nativeMin = Math.min; /** * Gets the timestamp of the number of milliseconds that have elapsed since * the Unix epoch (1 January 1970 00:00:00 UTC). * * @static * @memberOf _ * @since 2.4.0 * @category Date * @returns {number} Returns the timestamp. * @example * * _.defer(function(stamp) { * console.log(_.now() - stamp); * }, _.now()); * // => Logs the number of milliseconds it took for the deferred invocation. */ var now = function() { return root.Date.now(); }; /** * Creates a debounced function that delays invoking `func` until after `wait` * milliseconds have elapsed since the last time the debounced function was * invoked. The debounced function comes with a `cancel` method to cancel * delayed `func` invocations and a `flush` method to immediately invoke them. * Provide `options` to indicate whether `func` should be invoked on the * leading and/or trailing edge of the `wait` timeout. The `func` is invoked * with the last arguments provided to the debounced function. Subsequent * calls to the debounced function return the result of the last `func` * invocation. * * **Note:** If `leading` and `trailing` options are `true`, `func` is * invoked on the trailing edge of the timeout only if the debounced function * is invoked more than once during the `wait` timeout. * * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred * until to the next tick, similar to `setTimeout` with a timeout of `0`. * * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) * for details over the differences between `_.debounce` and `_.throttle`. * * @static * @memberOf _ * @since 0.1.0 * @category Function * @param {Function} func The function to debounce. * @param {number} [wait=0] The number of milliseconds to delay. * @param {Object} [options={}] The options object. * @param {boolean} [options.leading=false] * Specify invoking on the leading edge of the timeout. * @param {number} [options.maxWait] * The maximum time `func` is allowed to be delayed before it's invoked. * @param {boolean} [options.trailing=true] * Specify invoking on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * * // Avoid costly calculations while the window size is in flux. * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); * * // Invoke `sendMail` when clicked, debouncing subsequent calls. * jQuery(element).on('click', _.debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })); * * // Ensure `batchLog` is invoked once after 1 second of debounced calls. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); * var source = new EventSource('/stream'); * jQuery(source).on('message', debounced); * * // Cancel the trailing debounced invocation. * jQuery(window).on('popstate', debounced.cancel); */ function debounce(func, wait, options) { var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } wait = toNumber(wait) || 0; if (isObject(options)) { leading = !!options.leading; maxing = 'maxWait' in options; maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; trailing = 'trailing' in options ? !!options.trailing : trailing; } function invokeFunc(time) { var args = lastArgs, thisArg = lastThis; lastArgs = lastThis = undefined; lastInvokeTime = time; result = func.apply(thisArg, args); return result; } function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time; // Start the timer for the trailing edge. timerId = setTimeout(timerExpired, wait); // Invoke the leading edge. return leading ? invokeFunc(time) : result; } function remainingWait(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime, result = wait - timeSinceLastCall; return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; } function shouldInvoke(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating // it as the trailing edge, or we've hit the `maxWait` limit. return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); } function timerExpired() { var time = now(); if (shouldInvoke(time)) { return trailingEdge(time); } // Restart the timer. timerId = setTimeout(timerExpired, remainingWait(time)); } function trailingEdge(time) { timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) { return invokeFunc(time); } lastArgs = lastThis = undefined; return result; } function cancel() { if (timerId !== undefined) { clearTimeout(timerId); } lastInvokeTime = 0; lastArgs = lastCallTime = lastThis = timerId = undefined; } function flush() { return timerId === undefined ? result : trailingEdge(now()); } function debounced() { var time = now(), isInvoking = shouldInvoke(time); lastArgs = arguments; lastThis = this; lastCallTime = time; if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait); } return result; } debounced.cancel = cancel; debounced.flush = flush; return debounced; } /** * Checks if `value` is the * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(_.noop); * // => true * * _.isObject(null); * // => false */ function isObject(value) { var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. * @example * * _.isObjectLike({}); * // => true * * _.isObjectLike([1, 2, 3]); * // => true * * _.isObjectLike(_.noop); * // => false * * _.isObjectLike(null); * // => false */ function isObjectLike(value) { return !!value && typeof value == 'object'; } /** * Checks if `value` is classified as a `Symbol` primitive or object. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. * @example * * _.isSymbol(Symbol.iterator); * // => true * * _.isSymbol('abc'); * // => false */ function isSymbol(value) { return typeof value == 'symbol' || (isObjectLike(value) && objectToString.call(value) == symbolTag); } /** * Converts `value` to a number. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to process. * @returns {number} Returns the number. * @example * * _.toNumber(3.2); * // => 3.2 * * _.toNumber(Number.MIN_VALUE); * // => 5e-324 * * _.toNumber(Infinity); * // => Infinity * * _.toNumber('3.2'); * // => 3.2 */ function toNumber(value) { if (typeof value == 'number') { return value; } if (isSymbol(value)) { return NAN; } if (isObject(value)) { var other = typeof value.valueOf == 'function' ? value.valueOf() : value; value = isObject(other) ? (other + '') : other; } if (typeof value != 'string') { return value === 0 ? value : +value; } value = value.replace(reTrim, ''); var isBinary = reIsBinary.test(value); return (isBinary || reIsOctal.test(value)) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : (reIsBadHex.test(value) ? NAN : +value); } var lodash_debounce = debounce; /** * throttle * @param func * @param wait */ function throttle(func, wait) { return lodash_debounce(func, wait, { leading: true, maxWait: wait, trailing: true, }); } /** * 是否支持触摸设备 */ var isTouchDevice = typeof document !== 'undefined' && 'ontouchstart' in document.documentElement; /** * 从 Touch 事件中获取两个触控中心位置 */ function getMultipleTouchPosition(evt) { var _a = evt.touches[0], clientX = _a.clientX, clientY = _a.clientY; if (evt.touches.length >= 2) { var _b = evt.touches[1], nextClientX = _b.clientX, nextClientY = _b.clientY; return { clientX: (clientX + nextClientX) / 2, clientY: (clientY + nextClientY) / 2, touchLength: Math.sqrt(Math.pow(nextClientX - clientX, 2) + Math.pow(nextClientY - clientY, 2)), }; } return { clientX: clientX, clientY: clientY, touchLength: 0 }; } /** * 获取移动或缩放之后的中心点 */ function getPositionOnMoveOrScale(_a) { var x = _a.x, y = _a.y, clientX = _a.clientX, clientY = _a.clientY, _b = _a.offsetX, offsetX = _b === void 0 ? 0 : _b, _c = _a.offsetY, offsetY = _c === void 0 ? 0 : _c, fromScale = _a.fromScale, toScale = _a.toScale; var innerWidth = window.innerWidth, innerHeight = window.innerHeight; var centerClientX = innerWidth / 2; var centerClientY = innerHeight / 2; // 坐标偏移 var lastPositionX = centerClientX + x; var lastPositionY = centerClientY + y; // 放大偏移量 var offsetScale = toScale / fromScale; // 偏移位置 var originX = clientX - (clientX - lastPositionX) * offsetScale - centerClientX; var originY = clientY - (clientY - lastPositionY) * offsetScale - centerClientY; return { x: originX + offsetX, y: originY + offsetY, scale: toScale, lastMoveClientX: clientX, lastMoveClientY: clientY, }; } /** * 最大触摸时间 */ var maxTouchTime = 200; /** * 最大滑动切换图片距离 */ var maxMoveOffset = 40; /** * 图片的间隔 */ var horizontalOffset = 20; /** * 最小初始响应距离 */ var minStartTouchOffset = 10; /** * 默认背景透明度 */ var defaultOpacity = 1; /** * 最小缩放度 */ var minScale = 1; /** * 最大缩放度(若图片足够大,则会超出) */ var maxScale = 6; /** * 滑动加速度 */ var slideAcceleration = 0.005; /** * 缩放弹性缓冲 */ var scaleBuffer = 0.2; /** * 边缘超出状态 */ var CloseEdgeEnum; (function (CloseEdgeEnum) { CloseEdgeEnum[CloseEdgeEnum["Normal"] = 0] = "Normal"; CloseEdgeEnum[CloseEdgeEnum["Small"] = 1] = "Small"; CloseEdgeEnum[CloseEdgeEnum["Before"] = 2] = "Before"; CloseEdgeEnum[CloseEdgeEnum["After"] = 3] = "After"; })(CloseEdgeEnum || (CloseEdgeEnum = {})); /** * 边缘触发状态 */ var ReachTypeEnum; (function (ReachTypeEnum) { ReachTypeEnum[ReachTypeEnum["Normal"] = 0] = "Normal"; ReachTypeEnum[ReachTypeEnum["XReach"] = 1] = "XReach"; ReachTypeEnum[ReachTypeEnum["YReach"] = 2] = "YReach"; })(ReachTypeEnum || (ReachTypeEnum = {})); /** * 初始响应状态 */ var TouchStartEnum; (function (TouchStartEnum) { TouchStartEnum[TouchStartEnum["Normal"] = 0] = "Normal"; TouchStartEnum[TouchStartEnum["X"] = 1] = "X"; TouchStartEnum[TouchStartEnum["YPush"] = 2] = "YPush"; TouchStartEnum[TouchStartEnum["YPull"] = 3] = "YPull"; })(TouchStartEnum || (TouchStartEnum = {})); /** * 动画类型 */ var ShowAnimateEnum; (function (ShowAnimateEnum) { ShowAnimateEnum[ShowAnimateEnum["None"] = 0] = "None"; ShowAnimateEnum[ShowAnimateEnum["In"] = 1] = "In"; ShowAnimateEnum[ShowAnimateEnum["Out"] = 2] = "Out"; })(ShowAnimateEnum || (ShowAnimateEnum = {})); /** * 接触左边/上边 或 右边/下边边缘 * @param position - x/y * @param scale * @param size - width/height * @param innerSize - innerWidth/innerHeight * @return CloseEdgeEnum */ function getClosedEdge(position, scale, size, innerSize) { var currentWidth = size * scale; // 图片超出的宽度 var outOffsetX = (currentWidth - innerSize) / 2; if (currentWidth <= innerSize) { return CloseEdgeEnum.Small; } else if (position > 0 && outOffsetX - position <= 0) { return CloseEdgeEnum.Before; } else if (position < 0 && outOffsetX + position <= 0) { return CloseEdgeEnum.After; } return CloseEdgeEnum.Normal; } /** * 获取接触边缘类型 * @param initialTouchState * @param horizontalCloseEdge * @param verticalCloseEdge * @param reachState */ function getReachType(_a) { var initialTouchState = _a.initialTouchState, horizontalCloseEdge = _a.horizontalCloseEdge, verticalCloseEdge = _a.verticalCloseEdge, reachState = _a.reachState; if ((horizontalCloseEdge > 0 && initialTouchState === TouchStartEnum.X) || reachState === ReachTypeEnum.XReach) { return ReachTypeEnum.XReach; } else if ((verticalCloseEdge > 0 && (initialTouchState === TouchStartEnum.YPull || initialTouchState === TouchStartEnum.YPush)) || reachState === ReachTypeEnum.YReach) { return ReachTypeEnum.YReach; } return ReachTypeEnum.Normal; } /** * 适应到合适的图片偏移量 */ function slideToPosition(_a) { var _b; var x = _a.x, y = _a.y, lastX = _a.lastX, lastY = _a.lastY, width = _a.width, height = _a.height, scale = _a.scale, rotate = _a.rotate, touchedTime = _a.touchedTime; var moveTime = Date.now() - touchedTime; // 初始速度 var speedX = (x - lastX) / moveTime; var speedY = (y - lastY) / moveTime; // 停下所消耗时间 var slideTimeX = Math.abs(speedX / slideAcceleration); var slideTimeY = Math.abs(speedY / slideAcceleration); // 计划滑动位置 var planX = Math.floor(x + speedX * slideTimeX); var planY = Math.floor(y + speedY * slideTimeY); // 若图片不是水平则调换属性 if (rotate % 180 !== 0) { _b = [height, width], width = _b[0], height = _b[1]; } var currentX = planX; var currentY = planY; var innerWidth = window.innerWidth, innerHeight = window.innerHeight; // 图片超出的长度 var outOffsetX = (width * scale - innerWidth) / 2; var outOffsetY = (height * scale - innerHeight) / 2; var horizontalCloseEdge = getClosedEdge(planX, scale, width, innerWidth); var verticalCloseEdge = getClosedEdge(planY, scale, height, innerHeight); // x if (horizontalCloseEdge === CloseEdgeEnum.Small) { currentX = 0; } else if (horizontalCloseEdge === CloseEdgeEnum.Before) { currentX = outOffsetX; } else if (horizontalCloseEdge === CloseEdgeEnum.After) { currentX = -outOffsetX; } // y if (verticalCloseEdge === CloseEdgeEnum.Small) { currentY = 0; } else if (verticalCloseEdge === CloseEdgeEnum.Before) { currentY = outOffsetY; } else if (verticalCloseEdge === CloseEdgeEnum.After) { currentY = -outOffsetY; } // 时间过长 if (moveTime >= maxTouchTime && horizontalCloseEdge === CloseEdgeEnum.Normal && verticalCloseEdge === CloseEdgeEnum.Normal) { return { x: x, y: y, }; } return { x: currentX, y: currentY, }; } /** * 单击和双击事件处理 * @param singleTap - 单击事件 * @param doubleTap - 双击事件 * @return invokeTap */ function withContinuousTap(singleTap, doubleTap) { // 当前连续点击次数 var continuousClick = 0; var withDebounceTap = lodash_debounce(function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } continuousClick = 0; singleTap.apply(void 0, args); }, 300); return function invokeTap() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } continuousClick += 1; withDebounceTap.apply(void 0, args); // 双击 if (continuousClick >= 2) { withDebounceTap.cancel(); continuousClick = 0; doubleTap.apply(void 0, args); } }; } function getAnimateOrigin(originRect, width, height) { if (originRect) { var innerWidth_1 = window.innerWidth, innerHeight_1 = window.innerHeight; var xOrigin = (width - innerWidth_1) / 2 + originRect.clientX; var yOrigin = (height - innerHeight_1) / 2 + originRect.clientY; return xOrigin + "px " + yOrigin + "px"; } return undefined; } /** * 纠正缩放后偏离中心区域位置 */ function correctSuitablePosition(_a) { var x = _a.x, y = _a.y, scale = _a.scale; if (scale <= 1) { return { x: 0, y: 0, }; } return { x: x, y: y, }; } var initialState = { // 真实宽度 naturalWidth: 1, // 真实高度 naturalHeight: 1, // 宽度 width: 1, // 高度 height: 1, // 加载成功状态 loaded: false, // 破碎状态 broken: false, // 图片 X 偏移量 x: 0, // 图片 y 偏移量 y: 0, // 图片缩放程度 scale: 1, // 图片处于触摸的状态 touched: false, // 背景处于触摸状态 maskTouched: false, // 触摸开始时 x 原始坐标 clientX: 0, // 触摸开始时 y 原始坐标 clientY: 0, // 触摸开始时图片 x 偏移量 lastX: 0, // 触摸开始时图片 y 偏移量 lastY: 0, // 上一个触摸状态 x 原始坐标 lastMoveClientX: 0, // 上一个触摸状态 y 原始坐标 lastMoveClientY: 0, // 触摸开始时时间 touchedTime: 0, // 多指触控间距 lastTouchLength: 0, // 当前边缘触发状态 reachState: ReachTypeEnum.Normal, }; var PhotoView = /** @class */ (function (_super) { __extends(PhotoView, _super); function PhotoView(props) { var _this = _super.call(this, props) || this; _this.state = initialState; // 初始响应状态 _this.initialTouchState = TouchStartEnum.Normal; _this.handleImageLoad = function (imageParams) { _this.setState(imageParams); }; _this.handleResize = function () { var _a = _this.props, onPhotoResize = _a.onPhotoResize, rotate = _a.rotate; var _b = _this.state, loaded = _b.loaded, naturalWidth = _b.naturalWidth, naturalHeight = _b.naturalHeight; if (loaded) { _this.setState(getSuitableImageSize(naturalWidth, naturalHeight, rotate)); if (onPhotoResize) { onPhotoResize(); } } }; _this.handleStart = function (clientX, clientY, touchLength) { if (touchLength === void 0) { touchLength = 0; } _this.setState(function (prevState) { return ({ touched: true, clientX: clientX, clientY: clientY, lastMoveClientX: clientX, lastMoveClientY: clientY, lastX: prevState.x, lastY: prevState.y, lastTouchLength: touchLength, touchedTime: Date.now(), }); }); }; _this.onMove = function (newClientX, newClientY, touchLength) { var _a; if (touchLength === void 0) { touchLength = 0; } var _b = _this.props, onReachMove = _b.onReachMove, isActive = _b.isActive, rotate = _b.rotate; var _c = _this.state, naturalWidth = _c.naturalWidth, x = _c.x, y = _c.y, clientX = _c.clientX, clientY = _c.clientY, lastMoveClientX = _c.lastMoveClientX, lastMoveClientY = _c.lastMoveClientY, lastX = _c.lastX, lastY = _c.lastY, scale = _c.scale, lastTouchLength = _c.lastTouchLength, reachState = _c.reachState, touched = _c.touched, maskTouched = _c.maskTouched; if ((touched || maskTouched) && isActive) { var _d = _this.state, width = _d.width, height = _d.height; // 若图片不是水平则调换属性 if (rotate % 180 !== 0) { _a = [height, width], width = _a[0], height = _a[1]; } // 单指最小缩放下,以初始移动距离来判断意图 if (touchLength === 0 && _this.initialTouchState === TouchStartEnum.Normal) { var isStillX = Math.abs(newClientX - clientX) <= minStartTouchOffset; var isStillY = Math.abs(newClientY - clientY) <= minStartTouchOffset; // 初始移动距离不足 if (isStillX && isStillY) { // 方向记录上次移动距离,以便平滑过渡 _this.setState({ lastMoveClientX: newClientX, lastMoveClientY: newClientY, }); return; } // 设置响应状态 _this.initialTouchState = !isStillX ? TouchStartEnum.X : newClientY > clientY ? TouchStartEnum.YPull : TouchStartEnum.YPush; } var offsetX = newClientX - lastMoveClientX; var offsetY = newClientY - lastMoveClientY; // 边缘触发状态 var currentReachState = ReachTypeEnum.Normal; if (touchLength === 0) { // 边缘超出状态 var horizontalCloseEdge = getClosedEdge(offsetX + lastX, scale, width, window.innerWidth); var verticalCloseEdge = getClosedEdge(offsetY + lastY, scale, height, window.innerHeight); // 边缘触发检测 currentReachState = getReachType({ initialTouchState: _this.initialTouchState, horizontalCloseEdge: horizontalCloseEdge, verticalCloseEdge: verticalCloseEdge, reachState: reachState, }); // 接触边缘 if (currentReachState != ReachTypeEnum.Normal) { onReachMove(currentReachState, newClientX, newClientY, scale); } } // 横向边缘触发、背景触发禁用当前滑动 if (currentReachState === ReachTypeEnum.XReach || maskTouched) { _this.setState({ reachState: ReachTypeEnum.XReach, }); } else { // 目标倍数 var endScale = scale + ((touchLength - lastTouchLength) / 100 / 2) * scale; // 限制最大倍数和最小倍数 var toScale = Math.max(Math.min(endScale, Math.max(maxScale, naturalWidth / width)), minScale - scaleBuffer); _this.setState(__assign({ lastTouchLength: touchLength, reachState: currentReachState }, getPositionOnMoveOrScale({ x: x, y: y, clientX: newClientX, clientY: newClientY, offsetX: offsetX, offsetY: offsetY, fromScale: scale, toScale: toScale, }))); } } }; _this.onPhotoTap = function (clientX, clientY) { var onPhotoTap = _this.props.onPhotoTap; if (onPhotoTap) { onPhotoTap(clientX, clientY); } }; _this.onDoubleTap = function (clientX, clientY) { var _a = _this.state, width = _a.width, naturalWidth = _a.naturalWidth, x = _a.x, y = _a.y, scale = _a.scale, reachState = _a.reachState; if (reachState !== ReachTypeEnum.Normal) { return; } var position = getPositionOnMoveOrScale({ x: x, y: y, clientX: clientX, clientY: clientY, fromScale: scale, // 若图片足够大,则放大适应的倍数 toScale: scale !== 1 ? 1 : Math.max(2, naturalWidth / width), }); _this.setState(__assign(__assign({ clientX: clientX, clientY: clientY }, position), correctSuitablePosition(position))); }; _this.handleWheel = function (e) { var clientX = e.clientX, clientY = e.clientY, deltaY = e.deltaY; var _a = _this.state, width = _a.width, naturalWidth = _a.naturalWidth, reachState = _a.reachState; if (reachState !== ReachTypeEnum.Normal) { return; } _this.setState(function (_a) { var x = _a.x, y = _a.y, scale = _a.scale; var endScale = scale - deltaY / 100 / 2; // 限制最大倍数和最小倍数 var toScale = Math.max(Math.min(endScale, Math.max(maxScale, naturalWidth / width)), minScale); var position = getPositionOnMoveOrScale({ x: x, y: y, clientX: clientX, clientY: clientY, fromScale: scale, toScale: toScale, }); return __assign(__assign({ clientX: clientX, clientY: clientY }, position), correctSuitablePosition(position)); }); }; _this.handleMaskStart = function (clientX, clientY) { _this.setState(function (prevState) { return ({ maskTouched: true, clientX: clientX, clientY: clientY, lastX: prevState.x, lastY: prevState.y, }); }); }; _this.handleMaskMouseDown = function (e) { _this.handleMaskStart(e.clientX, e.clientY); }; _this.handleMaskTouchStart = function (e) { var _a = e.touches[0], clientX = _a.clientX, clientY = _a.clientY; _this.handleMaskStart(clientX, clientY); }; _this.handleTouchStart = function (e) { var _a = getMultipleTouchPosition(e), clientX = _a.clientX, clientY = _a.clientY, touchLength = _a.touchLength; _this.handleStart(clientX, clientY, touchLength); }; _this.handleMouseDown = function (e) { e.preventDefault(); _this.handleStart(e.clientX, e.clientY, 0); }; _this.handleTouchMove = function (e) { e.preventDefault(); var _a = getMultipleTouchPosition(e), clientX = _a.clientX, clientY = _a.clientY, touchLength = _a.touchLength; _this.onMove(clientX, clientY, touchLength); }; _this.handleMouseMove = function (e) { e.preventDefault(); _this.onMove(e.clientX, e.clientY); }; _this.handleUp = function (newClientX, newClientY) { // 重置响应状态 _this.initialTouchState = TouchStartEnum.Normal; var _a = _this.props, onReachUp = _a.onReachUp, onPhotoTap = _a.onPhotoTap, onMaskTap = _a.onMaskTap, isActive = _a.isActive, rotate = _a.rotate; var _b = _this.state, width = _b.width, height = _b.height, naturalWidth = _b.naturalWidth, x = _b.x, y = _b.y, lastX = _b.lastX, lastY = _b.lastY, scale = _b.scale, touchedTime = _b.touchedTime, clientX = _b.clientX, clientY = _b.clientY, touched = _b.touched, maskTouched = _b.maskTouched; if ((touched || maskTouched) && isActive) { var hasMove_1 = clientX !== newClientX || clientY !== newClientY; _this.setState(__assign({ touched: false, maskTouched: false, // 限制缩放 scale: Math.max(Math.min(scale, Math.max(maxScale, naturalWidth / width)), minScale), reachState: ReachTypeEnum.Normal }, (hasMove_1 ? slideToPosition({ x: x, y: y, lastX: lastX, lastY: lastY, width: width, height: height, scale: scale, rotate: rotate, touchedTime: touchedTime, }) : { x: x, y: y, })), function () { if (onReachUp) { onReachUp(newClientX, newClientY); } // 触发 Tap 事件 if (!hasMove_1) { if (touched && onPhotoTap) { _this.handlePhotoTap(newClientX, newClientY); } else if (maskTouched && onMaskTap) { onMaskTap(newClientX, newClientY); } } }); } }; _this.handleTouchEnd = function (e) { var _a = e.changedTouches[0], clientX = _a.clientX, clientY = _a.clientY; _this.handleUp(clientX, clientY); }; _this.handleMouseUp = function (e) { var clientX = e.clientX, clientY = e.clientY; _this.handleUp(clientX, clientY); }; _this.onMove = throttle(_this.onMove, 8); _this.handleResize = throttle(_this.handleResize, 8); // 单击与双击事件处理 _this.handlePhotoTap = withContinuousTap(_this.onPhotoTap, _this.onDoubleTap); return _this; } PhotoView.prototype.componentDidMount = function () { if (isTouchDevice) { window.addEventListener('touchmove', this.handleTouchMove, { passive: false }); window.addEventListener('touchend', this.handleTouchEnd, { passive: false }); } else { window.addEventListener('mousemove', this.handleMouseMove); window.addEventListener('mouseup', this.handleMouseUp); } window.addEventListener('resize', this.handleResize); }; PhotoView.prototype.componentDidUpdate = function (prevProps) { var rotate = this.props.rotate; if (rotate !== prevProps.rotate) { var _a = this.state, naturalWidth = _a.naturalWidth, naturalHeight = _a.naturalHeight; this.setState(getSuitableImageSize(naturalWidth, naturalHeight, rotate)); } }; PhotoView.prototype.componentWillUnmount = function () { window.removeEventListener('touchmove', this.handleTouchMove); window.removeEventListener('touchend', this.handleTouchEnd); window.removeEventListener('mousemove', this.handleMouseMove); window.removeEventListener('mouseup', this.handleMouseUp); window.removeEventListener('resize', this.handleResize); }; PhotoView.prototype.render = function () { var _a = this.props, src = _a.src, intro = _a.intro, viewClassName = _a.viewClassName, className = _a.className, style = _a.style, rotate = _a.rotate, loadingElement = _a.loadingElement, brokenElement = _a.brokenElement, isActive = _a.isActive, showAnimateType = _a.showAnimateType, originRect = _a.originRect; var _b = this.state, width = _b.width, height = _b.height, loaded = _b.loaded, x = _b.x, y = _b.y, scale = _b.scale, touched = _b.touched, broken = _b.broken; var transform = "translate3d(" + x + "px, " + y + "px, 0) scale(" + scale + ") rotate(" + rotate + "deg)"; return (React.createElement("div", { className: classNames('PhotoView__PhotoWrap', viewClassName), style: style }, React.createElement("div", { className: "PhotoView__PhotoMask", onMouseDown: !isTouchDevice && isActive ? this.handleMaskMouseDown : undefined, onTouchStart: isTouchDevice && isActive ? this.handleMaskTouchStart : undefined }), React.createElement("div", { className: classNames('PhotoView__PhotoBox', { PhotoView__animateIn: loaded && showAnimateType === ShowAnimateEnum.In, PhotoView__animateOut: loaded && showAnimateType === ShowAnimateEnum.Out, }), style: { transformOrigin: loaded ? getAnimateOrigin(originRect, 0, 0) : undefined, } }, React.createElement(Photo, { className: className, src: src, intro: intro, width: width, height: height, loaded: loaded, broken: broken, rotate: rotate, onMouseDown: isTouchDevice ? undefined : this.handleMouseDown, onTouchStart: isTouchDevice ? this.handleTouchStart : undefined, onWheel: this.handleWheel, style: { WebkitTransform: transform, transform: transform, transition: touched ? undefined : 'transform 0.5s cubic-bezier(0.25, 0.8, 0.25, 1)', }, onImageLoad: this.handleImageLoad, loadingElement: loadingElement, brokenElement: brokenElement })))); }; PhotoView.displayName = 'PhotoView'; return PhotoView; }(React.Component)); var SlideWrap = function (_a) { var className = _a.className, children = _a.children, restProps = __rest(_a, ["className", "children"]); var dialogNode = React.useRef(document.createElement('section')); var originalOverflowCallback = React.useRef(''); React.useEffect(function () { document.body.appendChild(dialogNode.current); var style = document.body.style; originalOverflowCallback.current = style.overflow; style.overflow = 'hidden'; return function () { style.overflow = originalOverflowCallback.current; // 清除容器 document.body.removeChild(dialogNode.current); }; }, []); return reactDom.createPortal(React.createElement("div", __assign({ className: classNames('PhotoView-SlideWrap', className) }, restProps), children), dialogNode.current); }; SlideWrap.displayName = 'SlideWrap'; function VisibleAnimationHandle(_a) { var visible = _a.visible, currentImage = _a.currentImage, children = _a.children; var _b = React.useState(visible), photoVisible = _b[0], updatePhotoVisible = _b[1]; var _c = React.useState(ShowAnimateEnum.None), showAnimateType = _c[0], updateAnimateStatus = _c[1]; var _d = React.useState(), originRect = _d[0], updateOriginRect = _d[1]; function onShowAnimateEnd() { updateAnimateStatus(ShowAnimateEnum.None); // Close if (showAnimateType === ShowAnimateEnum.Out) { updatePhotoVisible(false); } } React.useEffect(function () { var originRef = (currentImage || {}).originRef; if (originRef && originRef.nodeType === 1) { // 获取触发时节点位置 var _a = originRef.getBoundingClientRect(), top_1 = _a.top, left = _a.left, width = _a.width, height = _a.height; updateOriginRect({ clientX: left + width / 2, clientY: top_1 + height / 2, }); } else if (originRect && !originRef) { updateOriginRect(undefined); } if (visible) { updateAnimateStatus(ShowAnimateEnum.In); updatePhotoVisible(true); } else { updateAnimateStatus(ShowAnimateEnum.Out); } }, [visible]); return children({ photoVisible: photoVisible, showAnimateType: showAnimateType, originRect: originRect, onShowAnimateEnd: onShowAnimateEnd, }); } function Close(props) { return (React.createElement("svg", __assign({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", width: "44", height: "44", viewBox: "0 0 768 768" }, props), React.createElement("path", { fill: "#FFF", d: "M607.5 205.5l-178.5 178.5 178.5 178.5-45 45-178.5-178.5-178.5 178.5-45-45 178.5-178.5-178.5-178.5 45-45 178.5 178.5 178.5-178.5z" }))); } function ArrowLeft(props) { return (React.createElement("svg", __assign({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", width: "44", height: "44", viewBox: "0 0 768 768" }, props), React.createElement("path", { d: "M640.5 352.5v63h-390l178.5 180-45 45-256.5-256.5 256.5-256.5 45 45-178.5 180h390z" }))); } function ArrowRight(props) { return (React.createElement("svg", __assign({ version: "1.1", xmlns: "http://www.w3.org/2000/svg", width: "44", height: "44", viewBox: "0 0 768 768" }, props), React.createElement("path", { d: "M384 127.5l256.5 256.5-256.5 256.5-45-45 178.5-180h-390v-63h390l-178.5-180z" }))); } var PhotoSlider = /** @class */ (function (_super) { __extends(PhotoSlider, _super); function PhotoSlider(props) { var _this = _super.call(this, props) || this; _this.handleClose = function (evt) { var onClose = _this.props.onClose; var backdropOpacity = _this.state.backdropOpacity; onClose(evt); _this.setState({ overlayVisible: true, // 记录当前关闭时的透明度 lastBackdropOpacity: backdropOpacity, }); }; _this.handlePhotoTap = function () { var photoClosable = _this.props.photoClosable; if (photoClosable) { _this.handleClose(); } else { _this.setState(function (prevState) { return ({ overlayVisible: !prevState.overlayVisible, }); }); } }; _this.handlePhotoMaskTap = function () { var maskClosable = _this.props.maskClosable; if (maskClosable) { _this.handleClose(); } }; _this.handleResize = function () { var innerWidth = window.innerWidth; _this.setState(function (_a) { var photoIndex = _a.photoIndex; return { translateX: -(innerWidth + horizontalOffset) * photoIndex, lastClientX: undefined, lastClientY: undefined, shouldTransition: false, }; }); }; _this.handleRotate = function (rotating) { var _a = _this.state, photoIndex = _a.photoIndex, rotatingMap = _a.rotatingMap; rotatingMap.set(photoIndex, rotating); _this.setState({ rotatingMap: rotatingMap, }); }; _this.handleKeyDown = function (evt) { var visible = _this.props.visible; if (visible) { switch (evt.key) { case 'ArrowLeft': _this.handlePrevious(false); break; case 'ArrowRight': _this.handleNext(false); break; case 'Escape': _this.handleClose(); break; } } }; _this.handleReachVerticalMove = function (clientY, scale) { _this.setState(function (_a) { var lastClientY = _a.lastClientY, backdropOpacity = _a.backdropOpacity; if (lastClientY === undefined) { return { touched: true, lastClientY: clientY, backdropOpacity: backdropOpacity, canPullClose: true, }; } var offsetClientY = Math.abs(clientY - lastClientY); var opacity = Math.max(Math.min(defaultOpacity, defaultOpacity - offsetClientY / 100 / 4), 0); return { touched: true, lastClientY: lastClientY, backdropOpacity: scale === 1 ? opacity : defaultOpacity, canPullClose: scale === 1, }; }); }; _this.handleReachHorizontalMove = function (clientX) { var innerWidth = window.innerWidth;