UNPKG

react-image-video-viewer

Version:

A React lightbox that supports videos, images and pinch zooming on images. Optimized for mobile UI with swiping, but can be used on desktop as well.

441 lines 16 kB
function _inheritsLoose(t, o) { t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } import * as React from 'react'; import * as utils from './utils'; var MIN_SCALE = 1; var MAX_SCALE = 4; var SETTLE_RANGE = 0.001; var ADDITIONAL_LIMIT = 0.2; var DOUBLE_TAP_THRESHOLD = 300; var ANIMATION_SPEED = 0.01; var RESET_ANIMATION_SPEED = 0.01; var INITIAL_X = 0; var INITIAL_Y = 0; var INITIAL_SCALE = 1; var MOBILE_ICON_SIZE = 35; var DESKTOP_ICON_SIZE = 50; var ESCAPE_KEY = 27; var RIGHT_KEY = 39; var LEFT_KEY = 37; var ReactImageVideoLightbox = /*#__PURE__*/function (_React$Component) { function ReactImageVideoLightbox(props) { var _this; _this = _React$Component.call(this, props) || this; _this.state = { x: INITIAL_X, y: INITIAL_Y, scale: INITIAL_SCALE, width: window.innerWidth, height: window.innerHeight, index: _this.props.startIndex, swiping: false, loading: true, iconSize: window.innerWidth <= 500 ? MOBILE_ICON_SIZE : DESKTOP_ICON_SIZE, overflow: undefined }; _this.width = window.innerWidth; _this.height = window.innerHeight; _this.handleTouchStart = _this.handleTouchStart.bind(_this); _this.handleTouchMove = _this.handleTouchMove.bind(_this); _this.handleTouchEnd = _this.handleTouchEnd.bind(_this); _this.onResize = _this.onResize.bind(_this); _this.handleKeyDown = _this.handleKeyDown.bind(_this); _this.onNavigationCallback = _this.props.onNavigationCallback && typeof _this.props.onNavigationCallback === 'function' ? _this.props.onNavigationCallback : function () {}; return _this; } _inheritsLoose(ReactImageVideoLightbox, _React$Component); var _proto = ReactImageVideoLightbox.prototype; _proto.zoomTo = function zoomTo(scale) { var _this2 = this; var _frame = function frame() { if (_this2.state.scale === scale) return; var distance = scale - _this2.state.scale; var targetScale = _this2.state.scale + ANIMATION_SPEED * distance; _this2.zoom(utils.settle(targetScale, scale, SETTLE_RANGE)); _this2.animation = requestAnimationFrame(_frame); }; this.animation = requestAnimationFrame(_frame); }; _proto.reset = function reset() { var _this3 = this; var _frame2 = function frame() { if (_this3.state.scale === INITIAL_SCALE && _this3.state.x === INITIAL_X && _this3.state.y === INITIAL_Y) return; var scaleDelta = INITIAL_SCALE - _this3.state.scale; var targetScale = utils.settle(_this3.state.scale + RESET_ANIMATION_SPEED * scaleDelta, INITIAL_SCALE, SETTLE_RANGE); var nextWidth = _this3.width * targetScale; var nextHeight = _this3.height * targetScale; _this3.setState({ scale: targetScale, width: nextWidth, height: nextHeight, x: INITIAL_X, y: INITIAL_Y }, function () { _this3.animation = requestAnimationFrame(_frame2); }); }; this.animation = requestAnimationFrame(_frame2); }; _proto.handleTouchStart = function handleTouchStart(event) { this.animation && cancelAnimationFrame(this.animation); if (event.touches.length === 2) this.handlePinchStart(event); if (event.touches.length === 1) this.handleTapStart(event); }; _proto.handleTouchMove = function handleTouchMove(event) { if (event.touches.length === 2) this.handlePinchMove(event); if (event.touches.length === 1) this.handlePanMove(event); }; _proto.handleTouchEnd = function handleTouchEnd(event) { if (event && event.touches && event.touches.length > 0) return null; if (this.state.scale > MAX_SCALE) return this.zoomTo(MAX_SCALE); if (this.state.scale < MIN_SCALE) return this.zoomTo(MIN_SCALE); if (event && this.lastTouchEnd && this.lastTouchEnd + DOUBLE_TAP_THRESHOLD > event.timeStamp) { this.reset(); } if (this.state.swiping && this.state.scale === 1) { this.handleSwipe(event); } this.lastTouchEnd = event ? event.timeStamp : undefined; }; _proto.handleSwipe = function handleSwipe(event) { var swipeDelta = event ? event.changedTouches[0].clientX - this.swipeStartX : 0; if (swipeDelta < -(this.width / 3)) { this.swipeRight(); } else if (swipeDelta > this.width / 3) { this.swipeLeft(); } else { this.reset(); } }; _proto.swipeLeft = function swipeLeft() { var _this4 = this; var currentIndex = this.state.index; if (currentIndex > 0) { setTimeout(function () { _this4.setState({ index: currentIndex - 1, swiping: false, x: INITIAL_X, loading: true }, function () { return _this4.onNavigationCallback(currentIndex - 1); }); }, 0); } else { this.reset(); } }; _proto.swipeRight = function swipeRight() { var _this5 = this; var currentIndex = this.state.index; if (currentIndex < this.props.data.length - 1) { setTimeout(function () { _this5.setState({ index: currentIndex + 1, swiping: false, x: INITIAL_X, loading: true }, function () { return _this5.onNavigationCallback(currentIndex + 1); }); }, 0); } else { this.reset(); } }; _proto.handleTapStart = function handleTapStart(event) { this.swipeStartX = event.touches[0].clientX; this.swipeStartY = event.touches[0].clientY; if (this.state.scale === 1) { this.setState({ swiping: true }); } }; _proto.handlePanMove = function handlePanMove(event) { if (this.state.scale === 1) { this.setState({ x: event.touches[0].clientX - this.swipeStartX }); } else { event.preventDefault(); this.setState({ x: event.touches[0].clientX - this.swipeStartX, y: event.touches[0].clientY - this.swipeStartY }); } }; _proto.handlePinchStart = function handlePinchStart(event) { var pointA = utils.getPointFromTouch(event.touches[0]); var pointB = utils.getPointFromTouch(event.touches[1]); this.lastDistance = utils.getDistanceBetweenPoints(pointA, pointB); }; _proto.handlePinchMove = function handlePinchMove(event) { event.preventDefault(); var pointA = utils.getPointFromTouch(event.touches[0]); var pointB = utils.getPointFromTouch(event.touches[1]); var distance = utils.getDistanceBetweenPoints(pointA, pointB); var scale = utils.between(MIN_SCALE - ADDITIONAL_LIMIT, MAX_SCALE + ADDITIONAL_LIMIT, this.state.scale * (distance / this.lastDistance)); this.zoom(scale); this.lastDistance = distance; }; _proto.zoom = function zoom(scale) { var nextWidth = this.width * scale; var nextHeight = this.height * scale; this.setState({ width: nextWidth, height: nextHeight, scale: scale }); }; _proto.getResources = function getResources() { var _this6 = this; var items = []; var data = this.props.data; for (var i = 0; i < data.length; i++) { var resource = data[i]; if (resource.type === 'photo') { items.push(/*#__PURE__*/React.createElement("img", { key: i, className: "img-viewer", alt: resource.altTag, src: resource.url, style: { pointerEvents: this.state.scale === 1 ? 'auto' : 'none', maxWidth: '100%', maxHeight: '100%', transform: "translate(" + this.state.x + "px, " + this.state.y + "px) scale(" + this.state.scale + ")", transition: 'transform 0.5s ease-out' }, onLoad: function onLoad() { _this6.setState({ loading: false }); } })); } if (resource.type === 'video') { if (resource.url && (resource.url.includes('.mp4') || resource.url.includes('.mov'))) { items.push(/*#__PURE__*/React.createElement("video", { onLoadStart: function onLoadStart() { _this6.setState({ loading: false }); }, onLoadedData: function onLoadedData() { _this6.setState({ loading: false }); }, key: i, className: "video-viewer", preload: "metadata", poster: resource.poster ? resource.poster : null, playsInline: true, controls: true, autoPlay: resource.autoPlay ? true : false, src: resource.url }, /*#__PURE__*/React.createElement("source", { src: resource.url + "#t=0.1", type: "video/webm" }), /*#__PURE__*/React.createElement("source", { src: resource.url + "#t=0.1", type: "video/ogg" }), /*#__PURE__*/React.createElement("source", { src: resource.url + "#t=0.1", type: "video/mp4" }), /*#__PURE__*/React.createElement("source", { src: resource.url + "#t=0.1", type: "video/3gp" }))); } else { items.push(/*#__PURE__*/React.createElement("iframe", { key: i, width: "560", height: "315", src: resource.url, frameBorder: "0", allow: "autoplay; encrypted-media", title: resource.title, allowFullScreen: true, style: { pointerEvents: this.state.scale === 1 ? 'auto' : 'none', maxWidth: '100%', maxHeight: '100%', transform: "translate(" + this.state.x + "px, " + this.state.y + "px)", transition: 'transform 0.5s ease-out' }, onLoad: function onLoad() { _this6.setState({ loading: false }); } })); } } } return items; }; _proto.handleKeyDown = function handleKeyDown(event) { switch (event.keyCode) { case ESCAPE_KEY: document.body.style.overflow = this.state.overflow ? this.state.overflow : null; this.props.onCloseCallback(); break; case RIGHT_KEY: this.swipeRight(); break; case LEFT_KEY: this.swipeLeft(); break; default: break; } }; _proto.onResize = function onResize() { if (window.innerWidth <= 500) { this.setState({ iconSize: MOBILE_ICON_SIZE }); } else { this.setState({ iconSize: DESKTOP_ICON_SIZE }); } }; _proto.componentDidMount = function componentDidMount() { this.setState({ overflow: document.body.style.overflow }); document.body.style.overflow = 'hidden'; window.addEventListener('resize', this.onResize); document.addEventListener("keydown", this.handleKeyDown); }; _proto.componentWillUnmount = function componentWillUnmount() { document.body.style.overflow = this.state.overflow ? this.state.overflow : null; window.removeEventListener('resize', this.onResize); document.removeEventListener("keydown", this.handleKeyDown); }; _proto.render = function render() { var _this7 = this; var resources = this.getResources(); return /*#__PURE__*/React.createElement("div", { onTouchStart: this.handleTouchStart, onTouchMove: this.handleTouchMove, onTouchEnd: this.handleTouchEnd, style: { top: '0px', left: '0px', overflow: 'hidden', position: 'fixed', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'row', height: '100%', width: '100%', background: 'rgba(0, 0, 0, 0.5019607843)', backdropFilter: 'blur(2.1428571429em)', zIndex: 100 } }, this.props.showResourceCount && /*#__PURE__*/React.createElement("div", { style: { position: 'absolute', top: '0px', left: '0px', padding: '15px', color: 'white', fontWeight: 'bold' } }, /*#__PURE__*/React.createElement("span", null, this.state.index + 1), " / ", /*#__PURE__*/React.createElement("span", null, this.props.data.length)), /*#__PURE__*/React.createElement("div", { style: { position: 'absolute', top: '0px', right: '0px', padding: '10px', color: '#FFFFFF', cursor: 'pointer', fontSize: this.state.iconSize * 0.8 + "px" }, onClick: function onClick() { document.body.style.overflow = _this7.state.overflow ? _this7.state.overflow : null; _this7.props.onCloseCallback(); } }, /*#__PURE__*/React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", height: "36px", viewBox: "0 0 24 24", width: "36px", fill: "#FFFFFF" }, /*#__PURE__*/React.createElement("path", { d: "M0 0h24v24H0z", fill: "none" }), /*#__PURE__*/React.createElement("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }))), this.state.index + 1 != 1 ? /*#__PURE__*/React.createElement("div", { style: { position: 'absolute', left: '0px', zIndex: 1, color: '#FFFFFF', cursor: 'pointer', fontSize: this.state.iconSize + "px" }, onClick: function onClick() { _this7.swipeLeft(); } }, /*#__PURE__*/React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", height: "48px", viewBox: "0 0 24 24", width: "48px", fill: "#FFFFFF" }, /*#__PURE__*/React.createElement("path", { d: "M0 0h24v24H0z", fill: "none" }), /*#__PURE__*/React.createElement("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }))) : /*#__PURE__*/React.createElement(React.Fragment, null), this.state.index + 1 != this.props.data.length ? /*#__PURE__*/React.createElement("div", { style: { position: 'absolute', right: '0px', zIndex: 1, color: '#FFFFFF', cursor: 'pointer', fontSize: this.state.iconSize + "px" }, onClick: function onClick() { _this7.swipeRight(); } }, /*#__PURE__*/React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", height: "48px", viewBox: "0 0 24 24", width: "48px", fill: "#FFFFFF" }, /*#__PURE__*/React.createElement("path", { d: "M0 0h24v24H0z", fill: "none" }), /*#__PURE__*/React.createElement("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }))) : /*#__PURE__*/React.createElement(React.Fragment, null), this.state.loading && /*#__PURE__*/React.createElement("div", { style: { margin: 'auto', position: 'fixed' } }, /*#__PURE__*/React.createElement("style", null, "@keyframes react_image_video_spinner {\n 0% {\n transform: translate3d(-50 %, -50 %, 0) rotate(0deg);\n }\n 100% {\n transform: translate3d(-50%, -50%, 0) rotate(360deg);\n }\n }"), /*#__PURE__*/React.createElement("div", { style: { animation: '1.0s linear infinite react_image_video_spinner', border: 'solid 5px #ffffff', borderBottomColor: '#cfd0d1', borderRadius: '50%', height: 30, width: 30, position: 'fixed', transform: 'translate3d(-50%, -50%, 0)' } })), resources[this.state.index]); }; return ReactImageVideoLightbox; }(React.Component); export default ReactImageVideoLightbox;