├── .gitattributes ├── .gitignore ├── README.md ├── example ├── App.js ├── img │ ├── beach-large.jpg │ ├── beach-small.jpg │ ├── cat-large.jpg │ ├── cat-small.jpg │ ├── fall-large.jpg │ └── fall-small.jpg └── index.html ├── index.js ├── package.json ├── src ├── App.js └── ImageMagnifier.js └── webpack.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # ========================= 31 | # Operating System Files 32 | # ========================= 33 | 34 | # OSX 35 | # ========================= 36 | 37 | .DS_Store 38 | .AppleDouble 39 | .LSOverride 40 | 41 | # Thumbnails 42 | ._* 43 | 44 | # Files that might appear on external disk 45 | .Spotlight-V100 46 | .Trashes 47 | 48 | # Directories potentially created on remote AFP share 49 | .AppleDB 50 | .AppleDesktop 51 | Network Trash Folder 52 | Temporary Items 53 | .apdisk 54 | 55 | # Windows 56 | # ========================= 57 | 58 | # Windows image file caches 59 | Thumbs.db 60 | ehthumbs.db 61 | 62 | # Folder config file 63 | Desktop.ini 64 | 65 | # Recycle Bin used on file shares 66 | $RECYCLE.BIN/ 67 | 68 | # Windows Installer files 69 | *.cab 70 | *.msi 71 | *.msm 72 | *.msp 73 | 74 | # Windows shortcuts 75 | *.lnk 76 | 77 | # Webstorm 78 | 79 | .idea 80 | .idea/vcs.xml 81 | .idea/modules.xml 82 | .idea/react-image-magnifier.iml 83 | .idea/scopes/scope_settings.xml 84 | .idea/vcs.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-image-magnifier 2 | A react component that accepts a high-res source image and produces a magnifier window on mouse hover over the part of the image the cursor is over 3 | 4 | 5 | ## Demo 6 | 7 | ![](http://media.giphy.com/media/xTiTnidsMNlZlf9I2c/giphy.gif) 8 | 9 | 10 | ## Usage 11 | 12 | ```bash 13 | > npm install --save react-image-magnifier 14 | ``` 15 | 16 | ```jsx 17 | var ImageMagnifer = require('react-image-magnifier'); 18 | 19 | var App = React.createClass({ 20 | 21 | render () { 22 | return ( 23 | 36 | ); 37 | } 38 | }); 39 | ``` 40 | 41 | ## API (props) 42 | 43 | | Prop | Required | Default | Type | Description | 44 | | :------------ |:---:|:---------------:| :---------------| :-----| 45 | | `image` | YES | | `{ src, width, height }` | the src, size of the non-zoomed-in image | 46 | | `zoomImage` | YES | | `{ src, width, height }` | the src, size of the zoomed-in image | 47 | | `cursorOffset` | NO | `{ x: 0, y: 0 }` | `{ x, y }` | the offset of the zoom bubble from the cursor | 48 | | `size` | NO | `200` | `Number` | the size of the magnifier window | -------------------------------------------------------------------------------- /example/img/beach-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/beach-large.jpg -------------------------------------------------------------------------------- /example/img/beach-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/beach-small.jpg -------------------------------------------------------------------------------- /example/img/cat-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/cat-large.jpg -------------------------------------------------------------------------------- /example/img/cat-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/cat-small.jpg -------------------------------------------------------------------------------- /example/img/fall-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/fall-large.jpg -------------------------------------------------------------------------------- /example/img/fall-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelandrichardson/react-image-magnifier/1b1b977e9666b3b2b90017202b9f7b21cbb5a02d/example/img/fall-small.jpg -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Image Magnifier 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/ImageMagnifier'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-image-magnifier", 3 | "version": "1.0.0", 4 | "description": "A react component that accepts a high-res source image and produces a magnifier window on mouse hover over the part of the image the cursor is over", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/lelandrichardson/react-image-magnifier.git" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "react-component", 16 | "image", 17 | "magnifier" 18 | ], 19 | "author": "Leland Richardson", 20 | "license": "MIT", 21 | "dependencies": { 22 | "react": "^0.13.3" 23 | }, 24 | "devDependencies": { 25 | "es5-shim": "^4.1.3", 26 | "node-libs-browser": "^0.5.2", 27 | "webpack": "^1.9.10", 28 | "babel": "^5.4.7", 29 | "babel-core": "^5.4.7", 30 | "babel-loader": "^5.1.3", 31 | "babel-runtime": "^5.4.7" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // polyfills 2 | require('es5-shim'); 3 | require('es5-shim/es5-sham'); 4 | 5 | var React = require('react'); 6 | var ImageMagnifier = require('./ImageMagnifier'); 7 | 8 | var App = React.createClass({ 9 | 10 | render () { 11 | return ( 12 |
13 | 25 | 38 | 50 |
51 | ); 52 | } 53 | }); 54 | 55 | React.render(, document.getElementById("mount")); -------------------------------------------------------------------------------- /src/ImageMagnifier.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var Magnifier = React.createClass({ 4 | 5 | propTypes: { 6 | 7 | // the size of the magnifier window 8 | size: React.PropTypes.number.isRequired, 9 | 10 | // x position on screen 11 | x: React.PropTypes.number.isRequired, 12 | 13 | // y position on screen 14 | y: React.PropTypes.number.isRequired, 15 | 16 | // x position relative to the image 17 | offsetX: React.PropTypes.number.isRequired, 18 | 19 | // y position relative to the image 20 | offsetY: React.PropTypes.number.isRequired, 21 | 22 | // the offset of the zoom bubble from the cursor 23 | cursorOffset: React.PropTypes.shape({ 24 | x: React.PropTypes.number.isRequired, 25 | y: React.PropTypes.number.isRequired 26 | }).isRequired, 27 | 28 | // the size of the non-zoomed-in image 29 | smallImage: React.PropTypes.shape({ 30 | src: React.PropTypes.string.isRequired, 31 | width: React.PropTypes.number.isRequired, 32 | height: React.PropTypes.number.isRequired 33 | }).isRequired, 34 | 35 | // the size of the zoomed-in image 36 | zoomImage: React.PropTypes.shape({ 37 | src: React.PropTypes.string.isRequired, 38 | width: React.PropTypes.number.isRequired, 39 | height: React.PropTypes.number.isRequired 40 | }).isRequired 41 | }, 42 | 43 | render () { 44 | var props = this.props; 45 | var halfSize = props.size / 2; 46 | var magX = props.zoomImage.width / props.smallImage.width; 47 | var magY = props.zoomImage.height / props.smallImage.height; 48 | var bgX = -(props.offsetX*magX - halfSize); 49 | var bgY = -(props.offsetY*magY - halfSize); 50 | var isVisible = props.offsetY < props.smallImage.height && 51 | props.offsetX < props.smallImage.width && 52 | props.offsetY > 0 && 53 | props.offsetX > 0; 54 | return ( 55 |
68 |
76 |
77 | ); 78 | } 79 | }); 80 | 81 | function getOffset(el) { 82 | var x = 0; 83 | var y = 0; 84 | while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { 85 | x += el.offsetLeft - el.scrollLeft; 86 | y += el.offsetTop - el.scrollTop; 87 | el = el.offsetParent; 88 | } 89 | return { x, y }; 90 | } 91 | 92 | var ImageMagnifier = React.createClass({ 93 | 94 | propTypes: { 95 | 96 | // the size of the magnifier window 97 | size: React.PropTypes.number, 98 | 99 | // the offset of the zoom bubble from the cursor 100 | cursorOffset: React.PropTypes.shape({ 101 | x: React.PropTypes.number.isRequired, 102 | y: React.PropTypes.number.isRequired 103 | }), 104 | 105 | // the size of the non-zoomed-in image 106 | image: React.PropTypes.shape({ 107 | src: React.PropTypes.string.isRequired, 108 | width: React.PropTypes.number.isRequired, 109 | height: React.PropTypes.number.isRequired 110 | }).isRequired, 111 | 112 | // the size of the zoomed-in image 113 | zoomImage: React.PropTypes.shape({ 114 | src: React.PropTypes.string.isRequired, 115 | width: React.PropTypes.number.isRequired, 116 | height: React.PropTypes.number.isRequired 117 | }).isRequired 118 | }, 119 | 120 | portalElement: null, 121 | 122 | getDefaultProps () { 123 | return { 124 | size: 200, 125 | cursorOffset: { x: 0, y: 0 } 126 | }; 127 | }, 128 | 129 | getInitialState () { 130 | return { 131 | x: 0, 132 | y: 0, 133 | offsetX: -1, 134 | offsetY: -1 135 | }; 136 | }, 137 | 138 | componentDidMount() { 139 | document.addEventListener('mousemove', this.onMouseMove); 140 | if (!this.portalElement) { 141 | this.portalElement = document.createElement('div'); 142 | document.body.appendChild(this.portalElement); 143 | } 144 | this.componentDidUpdate(); 145 | }, 146 | 147 | componentWillUnmount() { 148 | document.removeEventListener('mousemove', this.onMouseMove); 149 | document.body.removeChild(this.portalElement); 150 | this.portalElement = null; 151 | }, 152 | 153 | onMouseMove (e) { 154 | var offset = getOffset(this.getDOMNode()); 155 | 156 | this.setState({ 157 | x: e.x + window.scrollX, 158 | y: e.y + window.scrollY, 159 | offsetX: e.x - offset.x, 160 | offsetY: e.y - offset.y 161 | }); 162 | }, 163 | 164 | componentDidUpdate() { 165 | React.render(, this.portalElement); 172 | }, 173 | 174 | render () { 175 | return ( 176 | 177 | ); 178 | } 179 | }); 180 | 181 | module.exports = ImageMagnifier; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | cache: true, 6 | entry: { 7 | App: './src/App' 8 | }, 9 | output: { 10 | path: path.join(__dirname, 'example'), 11 | publicPath: 'example', 12 | filename: '[name].js' 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | loader: 'babel', 20 | query: { 21 | optional: [ 22 | 'runtime', 23 | 'minification.propertyLiterals' 24 | ] 25 | } 26 | } 27 | ], 28 | noParse: /\.min\.js/ 29 | }, 30 | resolve: { 31 | modulesDirectories: ['src/Components', 'src/Views', 'src/Styles', 'node_modules'], 32 | extensions: ['', '.js', '.jsx', '.json'] 33 | }, 34 | plugins: [ 35 | new webpack.NoErrorsPlugin() 36 | ] 37 | }; --------------------------------------------------------------------------------