Skip to content

Commit

Permalink
Support Touch
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanselzer committed Nov 8, 2016
1 parent 1e437bc commit 075fe4f
Show file tree
Hide file tree
Showing 27 changed files with 1,290 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
dist/
npm-debug.log
node_modules
node_modules
.next
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# react-image-magnify

A React component displaying side by side enlarged image view, with tinted control-image mask.
A React component for desktop and touch environments.

Intended for desktop, but will be updated with touch experience soon.
Desktop displays a side by side enlarged image view, with tinted control-image mask.
Supports delaying hover and hover-off, which may help reduce unintentional triggering.

Supports arbitrary image sizes. Scales magnification automatically.
Touch displays an in place enlarged image view.
Supports press to pan. Does not interfere with scrolling.

Supports delaying hover and hover-off, which can help reduce unintentional triggering.
Supports arbitrary image sizes. Scales magnification automatically.

## Demo
[Basic demo](http://www.webpackbin.com/N18FshAaW)
[Desktop](https://react-image-magnify-egeoxscwwk.now.sh/)

[Touch](https://goo.gl/A6DZog)

See ReactImageMagnify.js tab.
<img src="./docs/qr-2.png?raw=true" alt="Touch Demo"/>

https://goo.gl/A6DZog

## Installation

Expand All @@ -20,6 +26,7 @@ npm install --save react-image-magnify
```

## Usage
### Desktop

```JSX
import ReactImageMagnify from 'react-image-magnify';
Expand All @@ -40,6 +47,27 @@ import ReactImageMagnify from 'react-image-magnify';
}}/>
...
```
### Touch

```JavaScript
import ReactImageMagnifyTouch from 'react-image-magnify';
...
<ReactImageMagnifyTouch {...{
largeImage: {
alt: 'large image description goes here',
src: 'https://example.com/large/image.jpg',
width: 1200,
height: 1800
},
smallImage: {
alt: 'small image description goes here',
src: 'https://example.com/small/image.jpg',
width: 400,
height: 600
}
}}/>
...
```

## Props API

Expand All @@ -53,7 +81,7 @@ import ReactImageMagnify from 'react-image-magnify';
| `hoverOffDelayInMs` | Number | No | 150 | Milliseconds to delay hover-off trigger. |
| `fadeDurationInMs` | Number | No | 300 | Milliseconds duration of magnified image fade in/fade out. |
| `imageStyle` | Object | No | | Style applied to small image element. |
| `lensStyle` | Object | No | | Style applied to tinted lens element. |
| `lensStyle` | Object | No | | Style applied to tinted lens element. Desktop only |
| `enlargedImageContainerStyle` | Object | No | | Style applied to enlarged image container element. |
| `enlargedImageStyle` | Object | No | | Style applied to enlarged image element. |

Expand Down
20 changes: 20 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "react-image-magnify",
"version": "1.0.0",
"description": "react-image-magnify example and demo",
"main": "index.js",
"author": "",
"license": "ISC",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^1.1.0",
"lodash.assign": "^4.2.0",
"lodash.clamp": "^4.0.3",
"react-cursor-position": "^1.1.0",
"react-hover-observer": "^1.1.0"
}
}
29 changes: 29 additions & 0 deletions demo/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { Component } from 'react';
import ReactImageMagnify from '../src/ReactImageMagnify';

class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
</div>
<ReactImageMagnify {...{
largeImage: {
alt: 'Wrist watch face',
src: 'static/large-a.jpg',
width: 1200,
height: 1800
},
smallImage: {
alt: 'Wrist watch face',
src: 'static/large-a.jpg',
width: 400,
height: 600
}
}} />
</div>
);
}
}

export default App;
63 changes: 63 additions & 0 deletions demo/pages/touch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { Component } from 'react';
import ReactImageMagnifyTouch from '../src/ReactImageMagnifyTouch';

class App extends Component {
constructor(props) {
super(props);
this.state = {
imageWidth: 0,
imageHeight: 0
};
}

componentDidMount() {
const rect = document.documentElement.getBoundingClientRect();
const screenWidth = rect.width;
const imageWidth = screenWidth - 300;

this.setState({
imageWidth,
imageHeight: imageWidth + (imageWidth / 2)
});
}

render() {
return (
<div>
<ReactImageMagnifyTouch {...{
style: {
margin: '20px auto 0'
},
enlargedImageContainerStyle: {
left: '0px',
border: 'none',
margin: '0px'
},
largeImage: {
alt: 'Wrist watch face',
src: 'static/large-a.jpg',
width: 1200,
height: 1800
},
smallImage: {
alt: 'Wrist watch face',
src: 'static/large-a.jpg',
width: 400,
height: 600
}
}} />
<div style={{
width: '400px',
height: '2000px',
margin: '0 auto',
font: '22px Arial'
}}>
<p>Press (long touch) image to magnify. Pan (drag) to traverse image.</p>
<p>Note the page can be scrolled when touch begins on image.</p>
</div>
</div>
);
}
}

export default App;
170 changes: 170 additions & 0 deletions demo/src/EnlargedImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React, { PropTypes } from 'react';
import clamp from 'lodash.clamp';
import { Image } from './ReactImageMagnify';

export const Point = PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired
});

export const ImageShape = PropTypes.shape({
alt: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired
});

export default React.createClass({

displayName: 'EnlargedImage',

getInitialState() {
return {
isTransitionEntering: false,
isTransitionActive: false,
isTransitionLeaving: false,
isTransitionDone: false
};
},

getDefaultProps() {
return {
fadeDurationInMs: 0
};
},

propTypes: {
containerClassName: PropTypes.string,
containerStyle: PropTypes.object,
cursorOffset: Point,
cursorPosition: Point,
fadeDurationInMs: PropTypes.number,
imageClassName: PropTypes.string,
imageStyle: PropTypes.object,
isHovering: PropTypes.bool,
largeImage: ImageShape,
smallImage: ImageShape
},

componentWillReceiveProps(nextProps) {
const { isHovering } = nextProps;

if (isHovering) {
this.setState({
isTransitionEntering: true
});

setTimeout(() => {
this.setState({
isTransitionEntering: false,
isTransitionActive: true
});
}, 0);
} else {
this.setState({
isTransitionActive: false,
isTransitionLeaving: true
});

setTimeout(() => {
this.setState({
isTransitionLeaving: false,
isTransitionDone: true
});
}, this.props.fadeDurationInMs);
}
},

render() {
const {
containerClassName,
containerStyle,
cursorOffset,
cursorPosition,
fadeDurationInMs,
imageClassName,
imageStyle,
isHovering,
largeImage,
smallImage,
} = this.props;

const {
isTransitionEntering,
isTransitionActive,
isTransitionLeaving
} = this.state;

const offsetRatio = {
x: largeImage.width / smallImage.width,
y: largeImage.height / smallImage.height
};

const differentiatedImageCoordinates = {
x: (Math.round((cursorPosition.x - cursorOffset.x) * offsetRatio.x) * -1),
y: (Math.round((cursorPosition.y - cursorOffset.y) * offsetRatio.y) * -1)
};

const minCoordinates = {
x: ((largeImage.width - smallImage.width) * -1),
y: ((largeImage.height - smallImage.height) * -1)
};

const maxCoordinate = 0;

const imageCoordinates = {
x: clamp(differentiatedImageCoordinates.x, minCoordinates.x, maxCoordinate),
y: clamp(differentiatedImageCoordinates.y, minCoordinates.y, maxCoordinate)
};

let isVisible;
if (isTransitionEntering || isTransitionActive || isTransitionLeaving) {
isVisible = true;
} else {
isVisible = false;
}

const defaultContainerStyle = {
marginLeft: '10px',
position: 'absolute',
left: '100%',
top: '0px',
border: '1px solid #d6d6d6',
overflow: 'hidden'
};

const computedContainerStyle = {
width: smallImage.width,
height: smallImage.height,
opacity: this.state.isTransitionActive ? 1 : 0,
transition: `opacity ${fadeDurationInMs}ms ease-in`
};

const translate = `translate(${imageCoordinates.x}px, ${imageCoordinates.y}px)`;

const computedImageStyle = {
width: largeImage.width,
height: largeImage.height,
transform: translate,
WebkitTransform: translate,
msTransform: translate
};

const component = (
<div { ...{
className: containerClassName,
key: 'enlarged',
style: Object.assign({}, defaultContainerStyle, containerStyle, computedContainerStyle)
}}>
<img data-hover="false" { ...{
alt: largeImage.alt,
className: imageClassName,
src: largeImage.src,
style: Object.assign({}, imageStyle, computedImageStyle)
}}/>
</div>
);

return isVisible ? component : null;
}
});
Loading

0 comments on commit 075fe4f

Please sign in to comment.