react-magic-zoom
Version:
React component for show zoom in image on mouse hover.
529 lines (432 loc) • 20.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /*jshint esnext: true */
var MagicZoom = function (_React$Component) {
_inherits(MagicZoom, _React$Component);
function MagicZoom(props) {
_classCallCheck(this, MagicZoom);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(MagicZoom).call(this, props));
_this.$image = undefined;
_this.$cursorFrame = undefined;
_this.$reflection = undefined;
_this.$imageWrapper = undefined;
_this.handleImageLoad = _this.handleImageLoad.bind(_this);
_this.handleMouseMoveOnImage = _this.handleMouseMoveOnImage.bind(_this);
_this.handleMouseLeaveFromImage = _this.handleMouseLeaveFromImage.bind(_this);
_this.handleMouseEnterOnImage = _this.handleMouseEnterOnImage.bind(_this);
_this.handleStopEvent = _this.handleStopEvent.bind(_this);
_this.delayedReflectionHandler = _lodash2.default.debounce(_this.reflectionSubscribersCall, 10);
_this.state = {
elementsState: {
imageWrapper: {
style: {}
},
reflection: {
size: {
height: 0,
width: 0
},
position: {},
background: {
position: {
x: 0,
y: 0
}
},
disabled: true
},
cursorFrame: {
//default
type: 'default',
overflow: false,
size: {
height: 0,
width: 0
},
position: {
x: 0,
y: 0,
offsetX: 0,
offsetY: 0
}
}
}
};
return _this;
}
_createClass(MagicZoom, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.initializeComponentState();
this.setState(this.state);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {}
// Initialization methods
}, {
key: 'initializeComponentState',
value: function initializeComponentState() {
// ToDo move changable props to state
// preset default option by component type
var state = this.state;
this.setState(state);
}
}, {
key: 'initializeReflection',
value: function initializeReflection() {
var state = this.state;
state.elementsState.reflection.scale = this.props.reflection.scale || 2;
if (this.props.reflection.type && this.props.reflection.type !== 'auto') {
state.elementsState.reflection.type = this.props.reflection.type;
}
if (this.$image) {
state.elementsState.imageWrapper.style = {
heigth: this.$image.height,
width: this.$image.width
};
}
// auto calculation
if (!this.props.reflection.size && this.$image) {
state.elementsState.reflection.size.height = this.$image.height;
state.elementsState.reflection.size.width = this.$image.width;
} else {
state.elementsState.reflection.size.height = this.props.reflection.size.height;
state.elementsState.reflection.size.width = this.props.reflection.size.width;
}
if (this.props.reflection.position === 'right' || this.props.reflection.position === 'left' || this.props.reflection.position === 'top' || this.props.reflection.position === 'bottom') {
state.elementsState.reflection.position['left'] = '0';
state.elementsState.reflection.position['top'] = '0';
state.elementsState.reflection.position[this.props.reflection.position] = '110%';
} else {
state.elementsState.reflection.position = this.props.reflection.position;
}
this.setState(state);
}
}, {
key: 'initializeCursorFrame',
value: function initializeCursorFrame() {
var state = this.state,
cursorFrameProps = this.props.cursorFrame,
cursorFrameState = state.elementsState.cursorFrame,
scale = this.props.reflection.scale || 2;
if (cursorFrameProps) {
if (cursorFrameProps.type) {
cursorFrameState.type = cursorFrameProps.type;
}
}
if (cursorFrameState.type === 'default' || !this.$image) {
cursorFrameState.position = {
x: 0,
y: 0,
offsetX: -80,
offsetY: -80
};
cursorFrameState.size = {
height: 160,
width: 160
};
} else if (cursorFrameState.type === 'auto') {
cursorFrameState.size = {
height: state.elementsState.reflection.size.height / scale,
width: state.elementsState.reflection.size.width / scale
};
cursorFrameState.position = {
x: 0,
y: 0,
offsetX: cursorFrameState.size.width / 2,
offsetY: cursorFrameState.size.height / 2
};
}
state.elementsState.cursorFrame = cursorFrameState;
this.setState(state);
}
}, {
key: 'preInitializeElement',
value: function preInitializeElement(element, elementName, options) {
var capitalizedName = elementName.charAt(0).toUpperCase() + elementName.slice(1);
if (!element) {
if (options && options.show) {
var state = this.state;
state.elementsState.reflection.disabled = false;
this.setState(state);
}
this['initialize' + capitalizedName]();
return this['get' + capitalizedName]();
} else {
return element;
}
}
// Handler methods
}, {
key: 'handleImageLoad',
value: function handleImageLoad(event) {
if (event.target.tagName.toLowerCase() === 'img') {
var state = this.state;
this.$image = event.target;
this.$reflection = this.preInitializeElement(null, 'reflection');
this.$cursorFrame = this.preInitializeElement(null, 'cursorFrame');
this.setState(state);
}
}
}, {
key: 'handleMouseMoveOnImage',
value: function handleMouseMoveOnImage(event) {
var state = this.state,
nativeEvent = event.nativeEvent,
reflectionElement = this.getDomElement('reflection'),
cursorFrame = this.getDomElement('cursorFrame'),
eventPoint = {};
if (event.type === 'mousemove') {
if (event.target === this.$image) {
eventPoint = {
x: nativeEvent.offsetX,
y: nativeEvent.offsetY
};
} else {
eventPoint = {
x: nativeEvent.target.offsetLeft + nativeEvent.offsetX,
y: nativeEvent.target.offsetTop + nativeEvent.offsetY
};
}
} else if (event.type === 'touchmove') {
eventPoint = {
x: nativeEvent.touches[0].clientX - this.refs.zoomWrapper.offsetLeft,
y: nativeEvent.touches[0].clientY - this.refs.zoomWrapper.offsetTop
};
event.preventDefault();
}
if (event.target === this.$image) {
this.calculateMouseAndCursorPositionByImage(state, eventPoint, reflectionElement, cursorFrame);
} else if (cursorFrame === event.target) {
this.calculateMouseAndCursorPositionByCursorFrame(state, eventPoint, cursorFrame);
}
this.setState(state);
}
}, {
key: 'handleMouseLeaveFromImage',
value: function handleMouseLeaveFromImage(event) {
var state = this.state;
state.elementsState.reflection.disabled = true;
this.setState(state);
}
}, {
key: 'handleMouseEnterOnImage',
value: function handleMouseEnterOnImage(event) {
var state = this.state;
state.elementsState.reflection.disabled = false;
// should update cursor postion
this.setState(state);
}
}, {
key: 'handleStopEvent',
value: function handleStopEvent(event) {
event.stopPropagation();
}
}, {
key: 'reflectionSubscribersCall',
value: function reflectionSubscribersCall(element) {
this.props.subscribeOnReflection && this.props.subscribeOnReflection(element);
}
// Calculators
}, {
key: 'calculateMouseAndCursorPositionByImage',
value: function calculateMouseAndCursorPositionByImage(state, eventPoint, reflectionElement, cursorFrame) {
state.elementsState.reflection.background.position.x = -(eventPoint.x * this.state.elementsState.reflection.scale - reflectionElement.offsetWidth / 2);
state.elementsState.reflection.background.position.y = -(eventPoint.y * this.state.elementsState.reflection.scale - reflectionElement.offsetHeight / 2);
// frame
if (!state.elementsState.cursorFrame.overflow) {
this.calculateCursorPositionByClosestBorder(state, eventPoint, {
width: this.$image ? this.$image.width : 0,
height: this.$image ? this.$image.height : 0
});
} else if (cursorFrame) {
state.elementsState.cursorFrame.position.y = eventPoint.y;
state.elementsState.cursorFrame.position.x = eventPoint.x;
}
}
}, {
key: 'calculateMouseAndCursorPositionByCursorFrame',
value: function calculateMouseAndCursorPositionByCursorFrame(state, eventPoint, cursorFrame) {
var imageSize = {};
imageSize = {
width: this.$image ? this.$image.width : 0,
height: this.$image ? this.$image.height : 0
};
// hide if mouse blur
if (eventPoint.x > imageSize.width || eventPoint.x < 0 || eventPoint.y > imageSize.height || eventPoint.y < 0) {
state.elementsState.reflection.disabled = true;
} else {
if (!state.elementsState.cursorFrame.overflow) {
this.calculateCursorPositionByClosestBorder(state, eventPoint, imageSize);
} else {
if (cursorFrame) {
state.elementsState.cursorFrame.position.x = eventPoint.x;
state.elementsState.cursorFrame.position.y = eventPoint.y;
}
}
state.elementsState.reflection.background.position.x = -(eventPoint.x * this.state.elementsState.reflection.scale - this.state.elementsState.reflection.size.width / 2);
state.elementsState.reflection.background.position.y = -(eventPoint.y * this.state.elementsState.reflection.scale - this.state.elementsState.reflection.size.height / 2);
}
}
}, {
key: 'calculateCursorPositionByClosestBorder',
value: function calculateCursorPositionByClosestBorder(state, eventPoint, imageSize) {
if (eventPoint.x + state.elementsState.cursorFrame.size.width / 2 >= imageSize.width || eventPoint.x - state.elementsState.cursorFrame.size.width / 2 <= 0) {
if (eventPoint.x + state.elementsState.cursorFrame.size.width / 2 >= imageSize.width) {
state.elementsState.cursorFrame.position.x = imageSize.width - state.elementsState.cursorFrame.size.width / 2;
}
if (eventPoint.x - state.elementsState.cursorFrame.size.width / 2 <= 0) {
state.elementsState.cursorFrame.position.x = state.elementsState.cursorFrame.size.width / 2;
}
} else {
state.elementsState.cursorFrame.position.x = eventPoint.x;
}
if (!state.elementsState.cursorFrame.overflow && eventPoint.y + state.elementsState.cursorFrame.size.height / 2 >= imageSize.height || eventPoint.y - state.elementsState.cursorFrame.size.height / 2 <= 0) {
if (eventPoint.y + state.elementsState.cursorFrame.size.height / 2 >= imageSize.height) {
state.elementsState.cursorFrame.position.y = imageSize.height - state.elementsState.cursorFrame.size.height / 2;
}
if (eventPoint.y - state.elementsState.cursorFrame.size.height / 2 <= 0) {
state.elementsState.cursorFrame.position.y = state.elementsState.cursorFrame.size.height / 2;
}
} else {
state.elementsState.cursorFrame.position.y = eventPoint.y;
}
}
}, {
key: 'getDomElement',
value: function getDomElement(refName, existReflection) {
var existElement = _reactDom2.default.findDOMNode(this.refs[refName]);
return existElement || this.preInitializeElement(existReflection, refName, {
show: true
});
}
// Pre-render methods
}, {
key: 'getCursorFrame',
value: function getCursorFrame() {
var cursorFrameSettings = this.state.elementsState.cursorFrame,
classNames = 'magic-zoom__cursor-frame',
style = {},
element;
if (this.state.elementsState.reflection.disabled) {
return null;
}
if (cursorFrameSettings.type === 'default') {
classNames += ' magic-zoom__cursor-frame--default';
} else if (cursorFrameSettings.type === 'auto') {
classNames += ' magic-zoom__cursor-frame--auto';
style = {
width: cursorFrameSettings.size.width + 'px',
height: cursorFrameSettings.size.height + 'px'
};
}
style.top = cursorFrameSettings.position.y - cursorFrameSettings.position.offsetY + 'px';
style.left = cursorFrameSettings.position.x - cursorFrameSettings.position.offsetX + 'px';
element = _react2.default.createElement('div', { ref: 'cursorFrame',
className: classNames,
style: style
});
return element;
}
}, {
key: 'getReflection',
value: function getReflection() {
var state = this.state,
reflectionSettings = this.state.elementsState.reflection,
style = {
height: reflectionSettings.size.height + 'px',
width: reflectionSettings.size.width + 'px'
},
element;
style = _lodash2.default.assign(style, reflectionSettings.position);
if (reflectionSettings.disabled) {
return null;
}
if (this.$image) {
style.backgroundImage = 'url(' + this.$image.src + ')';
style.backgroundSize = state.elementsState.imageWrapper.style.width * reflectionSettings.scale + 'px ' + state.elementsState.imageWrapper.style.heigth * reflectionSettings.scale + 'px';
style.backgroundPosition = reflectionSettings.background.position.x + 'px ' + reflectionSettings.background.position.y + 'px';
}
element = _react2.default.createElement('div', {
ref: 'reflection',
style: style,
className: 'magic-zoom__reflection',
onMouseMove: this.handleMouseLeaveFromImage,
onTouchMove: this.handleMouseLeaveFromImage
});
if (this.props.subscribeOnReflection) {
this.delayedReflectionHandler(element);
}
return element;
}
}, {
key: 'render',
value: function render() {
var cursorFrame = this.getCursorFrame(),
imageReflection = this.getReflection(),
wrapperStyle = this.state.elementsState.imageWrapper.style;
if (this.props.type === 'donor') {
imageReflection = null;
}
return _react2.default.createElement(
'div',
{
ref: 'zoomWrapper',
className: (0, _classnames2.default)('magic-zoom__wrapper', this.props.className),
onLoad: this.handleImageLoad,
onMouseMove: this.handleMouseMoveOnImage,
onMouseEnter: this.handleMouseEnterOnImage,
onMouseLeave: this.handleMouseLeaveFromImage,
onTouchMove: this.handleMouseMoveOnImage,
onTouchStart: this.handleMouseEnterOnImage,
onTouchEnd: this.handleMouseLeaveFromImage,
onTouchCancel: this.handleMouseLeaveFromImage,
style: wrapperStyle
},
this.props.children,
cursorFrame,
imageReflection
);
}
}]);
return MagicZoom;
}(_react2.default.Component);
exports.default = MagicZoom;
MagicZoom.propTypes = {
children: _react2.default.PropTypes.element.isRequired,
type: _react2.default.PropTypes.oneOf(['auto', 'custom', 'invider', 'donor'])
};
MagicZoom.defaultProps = {
// temporary for testing
type: 'auto',
cursorFrame: {
type: 'auto',
// can be {heigth: int, width: int} or auto string
size: 'auto',
// ablility of frame moving out of original image
overflow: true
},
reflection: {
type: 'auto',
// Value: 'left', 'right', 'top', 'bottom' - position of
// reflection
position: 'left',
// Value: @flaot - scale coefficient
scale: 2
}
};