Skip to content

Commit

Permalink
Merge pull request #1 from filipecorrea/hotspots
Browse files Browse the repository at this point in the history
Hotspots
  • Loading branch information
Filipe Corrêa authored Jul 17, 2019
2 parents 491e08f + c3793d4 commit 591c16d
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 21 deletions.
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,77 @@

React component for rendering images with zoom controls and hotspots.

## Prerequisites
## Install

Install from `npm` and include it in your project build process:

```
npm install react-image-hotspots --save
```

If your project uses [Yarn](https://yarnpkg.com/en/):

```
yarn add react-image-hotspots
```

## Usage

```jsx
import ImageHotspots from 'react-image-hotspots'

<ImageHotspots
src='https://raw.githubusercontent.com/filipecorrea/react-image-hotspots/master/src/landscape.jpg'
alt='Sample image'
hotspots={
[
{ x: 10, y: 30, content: <span>Hotspot 1</span> },
{ x: 40, y: 70, content: <span>Hotspot 2</span> },
{ x: 80, y: 30, content: <span>Hotspot 2</span> }
]
}
/>
```

### Component properties

| Props | Type | Default | Description |
|------------|------------------------------|---------|------------------------|
| `src` | String, _required_ | - | Image source |
| `alt` | String, _optional_ | "" | Image alternative info |
| `hotspots` | Array of objects, _optional_ | [] | Hotspots |

### Hotspot properties

| Props | Type | Default | Description |
|-----------|-----------------------------------|---------|--------------------------------|
| `x` | Number, _required_ | - | Percentage horizontal position |
| `y` | Number, _required_ | - | Percentage vertical position |
| `content` | React or HTML element, _required_ | - | Hotspot content |

## Development

### Prerequisites

- [Node.js 10](https://nodejs.org/dist/latest-v10.x/)

## Test
### Test

From project directory, run:

```
npm test
```

## Run
### Run

From project directory, run:

```
npm start
```

## Build
### Build

From project directory, run:

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-image-hotspots",
"version": "1.0.7",
"version": "1.0.8",
"description": "React component for rendering images with hotspots",
"main": "dist/ImageHotspots.js",
"files": [
Expand Down Expand Up @@ -43,6 +43,7 @@
"@storybook/react": "^5.1.9",
"babel-loader": "^8.0.6",
"jest": "^24.8.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"standard": "^12.0.1"
},
Expand Down
27 changes: 27 additions & 0 deletions src/Hotspot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'

class Hotspot extends React.Component {
render () {
const { x, y, content } = this.props

const hotspotStyle = {
position: 'absolute',
display: 'block',
top: x + '%',
left: y + '%',
fontFamily: 'Sans-Serif',
background: '#fff',
boxShadow: '0px 0px 2px 0px rgba(0,0,0,0.5)'
}
return <div style={hotspotStyle}>{content}</div>
}
}

Hotspot.propTypes = {
x: PropTypes.number,
y: PropTypes.number,
content: PropTypes.element
}

export default Hotspot
54 changes: 45 additions & 9 deletions src/ImageHotspots.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import Hotspot from './Hotspot'

class ImageHotspots extends React.Component {
constructor (props) {
Expand All @@ -18,7 +20,8 @@ class ImageHotspots extends React.Component {
scale: undefined,
ratio: undefined,
orientation: undefined
}
},
hotspots: []
}

this.container = React.createRef()
Expand All @@ -31,8 +34,9 @@ class ImageHotspots extends React.Component {
componentDidMount () {
const { offsetWidth: width, offsetHeight: height } = this.container.current
const orientation = (width > height) ? 'landscape' : 'portrait'
const hotspots = this.props.hotspots

this.setState({ container: { width, height, orientation } })
this.setState({ container: { width, height, orientation }, hotspots })

window.addEventListener('resize', this.onWindowResize)
}
Expand All @@ -42,7 +46,7 @@ class ImageHotspots extends React.Component {
}

render () {
const { src, alt } = this.props
const { src, alt, hotspots } = this.props
const { container, image } = this.state
const imageLoaded = image.initialWidth && image.initialHeight

Expand All @@ -57,6 +61,14 @@ class ImageHotspots extends React.Component {

let imageStyle = {}

const hotspotsStyle = {
position: 'absolute',
top: 0,
left: 0,
right: 0,
margin: 'auto'
}

const controlsStyle = {
position: 'absolute',
bottom: 10,
Expand Down Expand Up @@ -97,6 +109,9 @@ class ImageHotspots extends React.Component {
}

if (image.orientation === 'landscape') {
hotspotsStyle.height = image.width / image.ratio
hotspotsStyle.width = image.width

minimapStyle.width = 100 * image.ratio
minimapStyle.height = 100

Expand All @@ -105,6 +120,9 @@ class ImageHotspots extends React.Component {
: (100 * image.ratio) / (image.width / container.width)
guideStyle.height = 100 / image.scale
} else {
hotspotsStyle.height = image.height
hotspotsStyle.width = image.height / image.ratio

minimapStyle.width = 100
minimapStyle.height = 100 * image.ratio

Expand All @@ -118,6 +136,16 @@ class ImageHotspots extends React.Component {
return (
<div ref={this.container} style={containerStyle}>
<img src={src} alt={alt} onLoad={this.onImageLoad} style={imageStyle} />
{
hotspots &&
<div style={hotspotsStyle}>
{
hotspots.map(({ x, y, content }) => {
return <Hotspot x={x} y={y} content={content} />
})
}
</div>
}
<div style={controlsStyle}>
<button style={buttonStyle} onClick={() => this.zoom(1)}>Fit</button>
<br />
Expand All @@ -138,18 +166,20 @@ class ImageHotspots extends React.Component {
const { container } = this.state
const orientation = (initialWidth > initialHeight) ? 'landscape' : 'portrait'
const ratio = (orientation === 'landscape') ? initialWidth / initialHeight : initialHeight / initialWidth
const width = (container.orientation === 'landscape')
? container.height * ratio
: container.width
const height = (container.orientation === 'landscape')
? container.height
: container.width * ratio

this.setState((prevState) => ({
image: {
...prevState.image,
initialWidth,
initialHeight,
width: (container.orientation === 'landscape')
? container.height * ratio
: container.width,
height: (container.orientation === 'landscape')
? container.height
: container.width * ratio,
width,
height,
scale: 1,
ratio,
orientation
Expand Down Expand Up @@ -180,4 +210,10 @@ class ImageHotspots extends React.Component {
}
}

ImageHotspots.propTypes = {
src: PropTypes.string,
alt: PropTypes.string,
hotspots: PropTypes.array
}

export default ImageHotspots
21 changes: 15 additions & 6 deletions src/ImageHotspots.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { withKnobs, text } from '@storybook/addon-knobs'
import { withKnobs, text, object } from '@storybook/addon-knobs'
import ImageHotspots from './ImageHotspots'

import landscape from './landscape.jpg'
Expand All @@ -10,12 +10,17 @@ const stories = storiesOf('ImagesHotspots', module)

stories.addDecorator(withKnobs)

const hotspots = [
{ x: 10, y: 30, content: <span style={{ padding: '10px' }}>Hotspot</span> }
]

stories.add('default', () => {
return (
<div style={{ width: '100%', height: '90vh' }}>
<ImageHotspots
src={text('Image', landscape)}
alt={text('Alternate text', 'Theatro da Paz')}
alt={text('Alternate text', 'Sample image')}
hotspots={object('Hotspots', hotspots)}
/>
</div>
)
Expand All @@ -26,7 +31,8 @@ stories.add('landscape image & landscape container', () => {
<div style={{ width: '450px', height: '300px' }}>
<ImageHotspots
src={text('Image', landscape)}
alt={text('Alternate text', 'Theatro da Paz')}
alt={text('Alternate text', 'Sample image')}
hotspots={object('Hotspots', hotspots)}
/>
</div>
)
Expand All @@ -37,7 +43,8 @@ stories.add('landscape image & portrait container', () => {
<div style={{ width: '250px', height: '300px' }}>
<ImageHotspots
src={text('Image', landscape)}
alt={text('Alternate text', 'Theatro da Paz')}
alt={text('Alternate text', 'Sample image')}
hotspots={object('Hotspots', hotspots)}
/>
</div>
)
Expand All @@ -48,7 +55,8 @@ stories.add('portrait image & landscape container', () => {
<div style={{ width: '450px', height: '300px' }}>
<ImageHotspots
src={text('Image', portrait)}
alt={text('Alternate text', 'Theatro da Paz')}
alt={text('Alternate text', 'Sample image')}
hotspots={object('Hotspots', hotspots)}
/>
</div>
)
Expand All @@ -59,7 +67,8 @@ stories.add('portrait image & portrait container', () => {
<div style={{ width: '225px', height: '300px' }}>
<ImageHotspots
src={text('Image', portrait)}
alt={text('Alternate text', 'Theatro da Paz')}
alt={text('Alternate text', 'Sample image')}
hotspots={object('Hotspots', hotspots)}
/>
</div>
)
Expand Down

0 comments on commit 591c16d

Please sign in to comment.