react-photo-view-fix
Version:
一款精致的 React 的图片预览组件
1,357 lines (1,284 loc) • 72.3 kB
JavaScript
'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;