├── .gitignore
├── .npmignore
├── LICENSE
├── Makefile
├── README.md
├── examples
├── bigger-version-of-houses.jpg
├── examples.cjsx
├── houses.jpg
├── index.cjsx
├── index.html
├── ocean.jpg
├── ocean@2x.jpg
├── path.jpg
├── tower.jpg
└── tower@2x.jpg
├── package.json
├── src
└── index.cjsx
├── webpack.config.js
└── webpack.production.config.js
/.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 | dist/
30 | examples/bundle.js
31 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
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 | src/
30 | examples/bundle.js
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Kyle Mathews
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BIN = ./node_modules/.bin
2 |
3 | release-patch:
4 | @$(call release,patch)
5 |
6 | release-minor:
7 | @$(call release,minor)
8 |
9 | release-major:
10 | @$(call release,major)
11 |
12 | build:
13 | @$(BIN)/cjsx -cb -o dist src/index.cjsx
14 | @$(BIN)/webpack
15 |
16 | publish:
17 | git push --tags origin HEAD:master
18 | @$(BIN)/cjsx -cb -o dist src/index.cjsx
19 | npm publish
20 |
21 | publish-gh-pages:
22 | git checkout gh-pages
23 | git merge master
24 | @$(BIN)/webpack --config webpack.production.config.js
25 | cp examples/* .
26 | git add --all .
27 | git commit -m "New release"
28 | git push origin gh-pages
29 | git checkout master
30 |
31 | define release
32 | npm version $(1)
33 | endef
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-retina-image
2 | ==================
3 |
4 | React component for serving high-resolution images to devices with retina displays
5 |
6 | ## Demo
7 | http://kyleamathews.github.io/react-retina-image/
8 |
9 | ## Install
10 | `npm install react-retina-image`
11 |
12 | ## Usage
13 |
14 | Available props:
15 |
16 | * `checkIfRetinaImgExists` — test if retina image exists before swapping
17 | it in. If you're sure there's a retina image available, it's safe to
18 | set this to false. Defaults to true.
19 | * `forceOriginalDimensions` — sets width/height of retina image to
20 | original image. Note, this doesn't work if `checkIfRetinaImgExists` is set to
21 | false as then the original image is never loaded. In this case you'll
22 | need to set the width manually either as a prop or using css. Defaults to true.
23 | * `retinaImageSuffix` — defaults to `@2x` but you can change this if you
24 | use a different naming convention.
25 | * `onLoad` — handle the image onLoad event.
26 | * `onError` — handle the image onError event.
27 | * `src` — string or array for the image srcs. [See the
28 | demo](http://kyleamathews.github.io/react-retina-image/) for examples
29 | of how to format your src string or array.
30 |
31 | ```javascript
32 | var React = require('react');
33 | var RetinaImage = require('react-retina-image');
34 |
35 | React.createClass({
36 | render: function () {
37 |
38 | }
39 | });
40 |
41 | // Can also pass in array of srcs.
42 | React.createClass({
43 | render: function () {
44 |
46 | }
47 | });
48 | ```
49 |
50 | ## Attribution
51 | This component is largely a port of
52 | [retina.js](http://imulus.github.io/retinajs/) to React.js
53 |
--------------------------------------------------------------------------------
/examples/bigger-version-of-houses.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/bigger-version-of-houses.jpg
--------------------------------------------------------------------------------
/examples/examples.cjsx:
--------------------------------------------------------------------------------
1 | React = require('react')
2 | RetinaImage = require '../src/index'
3 | isRetina = require 'is-retina'
4 | _ = require 'underscore'
5 |
6 | module.exports = class Examples extends React.Component
7 | constructor: (props) ->
8 | super props
9 | @state =
10 | picsArray: [
11 | './tower.jpg'
12 | './path.jpg'
13 | './ocean.jpg'
14 | ]
15 | picIndex: 0
16 |
17 | render: ->
18 |
19 |
React-retina-image
20 |
Code on Github
21 |
22 |
23 | {if isRetina() then
Your screen is retina!
else
Your screen is not retina
}
24 |
25 | {if isRetina()
26 |
This image loaded its retina version after checking if it exists
27 | else
28 |
This image won't load its retina version
29 | }
30 |
31 | {"""
32 |
33 | """}
34 |
35 |
36 |
37 |
This image doesn't have a @2x version so stays at its lower resolution version
38 |
39 | {"""
40 |
41 | """}
42 |
43 |
44 |
45 |
If you know there's a retina image available, you can disable the check.
46 |
47 | {"""
48 |
52 | """}
53 |
54 |
55 |
56 |
If you don't have predictable names for the retina and non-retina
57 | versions of images, you can simply pass in an array of images as src where
58 | the first src is the non-retina version and the second is the retina version.
59 |
60 |
61 | {"""
62 |
64 | """}
65 |
66 |
70 |
71 |
For testing updates. Click on the image and it'll cycle forward
72 | through pictures
73 |
74 | {"""
75 |
79 | """}
80 |
81 |
85 |
86 |
87 |
88 | cyclePics: =>
89 | newIndex = @state.picIndex + 1
90 | if newIndex > 2
91 | newIndex = 0
92 |
93 | @setState picIndex: newIndex
94 |
--------------------------------------------------------------------------------
/examples/houses.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/houses.jpg
--------------------------------------------------------------------------------
/examples/index.cjsx:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | ReactDOM = require 'react-dom'
3 | Examples = require './examples'
4 |
5 | # Rendering components directly into document.body is discouraged,
6 | # since its children are often manipulated by third-party scripts and browser extensions.
7 | # This may lead to subtle reconciliation issues.
8 | containerEl = document.createElement('DIV')
9 | document.body.appendChild(containerEl)
10 | ReactDOM.render(, containerEl)
11 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React Retina Image
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/ocean.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/ocean.jpg
--------------------------------------------------------------------------------
/examples/ocean@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/ocean@2x.jpg
--------------------------------------------------------------------------------
/examples/path.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/path.jpg
--------------------------------------------------------------------------------
/examples/tower.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/tower.jpg
--------------------------------------------------------------------------------
/examples/tower@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/react-retina-image/1703a0b946a53f1aaa1d6e2293b76b0dff2f9205/examples/tower@2x.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-retina-image",
3 | "description": "React component for serving high-resolution images to devices with retina displays",
4 | "version": "2.0.5",
5 | "author": "Kyle Mathews ",
6 | "bugs": {
7 | "url": "https://github.com/KyleAMathews/react-retina-image/issues"
8 | },
9 | "dependencies": {
10 | "array-equal": "^1.0.0",
11 | "image-exists": "^1.1.0",
12 | "is-retina": "^1.0.3",
13 | "object-assign": "^4.1.0",
14 | "prop-types": "^15.5.6"
15 | },
16 | "devDependencies": {
17 | "cjsx-loader": "^3.0.0",
18 | "coffee-loader": "^0.7.2",
19 | "coffee-react": "^5.0.0",
20 | "coffee-script": "^1.11.0",
21 | "css-loader": "^0.25.0",
22 | "gulp": "^3.9.1",
23 | "react": "^15.5.6 || ^16.0.0",
24 | "react-dom": "^15.5.6 || ^16.0.0",
25 | "style-loader": "^0.13.1",
26 | "underscore": "^1.8.3",
27 | "webpack": "^1.13.2",
28 | "webpack-dev-server": "^1.16.1"
29 | },
30 | "peerDependencies": {
31 | "react": "^15.5.6 || ^16.0.0",
32 | "react-dom": "^15.5.6 || ^16.0.0"
33 | },
34 | "directories": {
35 | "example": "examples"
36 | },
37 | "homepage": "https://github.com/KyleAMathews/react-retina-image",
38 | "keywords": [
39 | "image",
40 | "react",
41 | "react-component",
42 | "retina"
43 | ],
44 | "license": "MIT",
45 | "main": "dist/index.js",
46 | "repository": {
47 | "type": "git",
48 | "url": "https://github.com/KyleAMathews/react-retina-image.git"
49 | },
50 | "scripts": {
51 | "test": "echo \"Error: no test specified\" && exit 1",
52 | "watch": "./node_modules/.bin/webpack-dev-server"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/index.cjsx:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | PropTypes = require 'prop-types'
3 | isRetina = require 'is-retina'
4 | imageExists = require 'image-exists'
5 | path = require 'path'
6 | assign = require 'object-assign'
7 | arrayEqual = require 'array-equal'
8 |
9 | module.exports = class RetinaImage extends React.Component
10 | @propTypes:
11 | src: PropTypes.oneOfType([
12 | PropTypes.string
13 | PropTypes.array
14 | ]).isRequired
15 | checkIfRetinaImgExists: PropTypes.bool
16 | forceOriginalDimensions: PropTypes.bool
17 | retinaImageSuffix: PropTypes.string
18 | handleOnLoad: PropTypes.func # Deprecated.
19 | onLoad: PropTypes.func
20 | onError: PropTypes.func
21 |
22 | @defaultProps:
23 | checkIfRetinaImgExists: true
24 | forceOriginalDimensions: true
25 | retinaImageSuffix: '@2x'
26 | onError: ->
27 |
28 | constructor: (props) ->
29 | super props
30 | @state = @wrangleProps()
31 |
32 | componentWillReceiveProps: (nextProps) ->
33 | isEqual = true
34 | if Array.isArray(@props.src) and Array.isArray(nextProps.src)
35 | isEqual = arrayEqual(@props.src, nextProps.src)
36 | else
37 | isEqual = @props.src is nextProps.src
38 |
39 | unless isEqual
40 | @setState assign @wrangleProps(nextProps), {
41 | width: null
42 | height: null
43 | imgLoaded: null
44 | retinaImgExists: null
45 | retinaCheckComplete: null
46 | }
47 |
48 | componentDidMount: ->
49 | @checkForRetina()
50 | @checkLoaded()
51 |
52 | componentDidUpdate: ->
53 | @checkForRetina()
54 |
55 | render: ->
56 | # Propagate only the props that `
` supports, avoid React `Unknown props` warning. https://fb.me/react-unknown-prop
57 | # CoffeeScript does not support splat `...` for object destructuring so using `assign` and `delete`. http://stackoverflow.com/a/20298038
58 | imgProps = assign {}, @props
59 | delete imgProps.src
60 | delete imgProps.checkIfRetinaImgExists
61 | delete imgProps.forceOriginalDimensions
62 | delete imgProps.retinaImageSuffix
63 | delete imgProps.handleOnLoad
64 | delete imgProps.onLoad
65 | delete imgProps.onError
66 |
67 | # Override some of the props for `
`.
68 | imgProps.src = @state.src
69 | imgProps.onLoad = @handleOnLoad
70 | imgProps.onError = @props.onError
71 |
72 | if @state.width >= 0
73 | imgProps.width = @state.width
74 |
75 | if @state.height >= 0
76 | imgProps.height = @state.height
77 |
78 |
81 |
82 | # src can be a href or an array of hrefs.
83 | wrangleProps: (props=@props) ->
84 | if Array.isArray(props.src)
85 | return {
86 | src: props.src[0]
87 | srcIsArray: true
88 | }
89 | else
90 | return {
91 | src: props.src
92 | srcIsArray: false
93 | }
94 |
95 | checkForRetina: ->
96 | if @state.retinaCheckComplete then return
97 |
98 | if isRetina() and @props.checkIfRetinaImgExists
99 | imageExists @getRetinaPath(), (exists) =>
100 | # If original image has loaded already (we have to wait so we know
101 | # the original image dimensions), then set the retina image path.
102 | if exists and @state?.imgLoaded
103 | @setState src: @getRetinaPath()
104 | else if exists
105 | @setState retinaImgExists: true
106 |
107 | @setState retinaCheckComplete: true
108 |
109 | # If the check isn't needed, immediately swap in the retina path
110 | else if isRetina() and not @props.checkIfRetinaImgExists
111 | @setState src: @getRetinaPath()
112 |
113 | @setState retinaCheckComplete: true
114 |
115 | # For server-rendered code, sometimes images will already be loaded by the time
116 | # this module mounts.
117 | # http://stackoverflow.com/a/1977898
118 | checkLoaded: ->
119 | el = @refs.img
120 |
121 | unless el.complete
122 | return false
123 |
124 | if el.naturalWidth is 0
125 | return false
126 |
127 | # No other way to disprove it's loaded so we'll assume it's ok.
128 | @handleOnLoad()
129 |
130 |
131 | handleOnLoad: (e) =>
132 | # Customers of component might care when the image loads.
133 | if @props.onLoad?
134 | @props.onLoad(e)
135 | # handleOnLoad was in an earlier version (wrong name) and will be removed
136 | # at the next major release.
137 | if @props.handleOnLoad?
138 | @props.handleOnLoad(e)
139 |
140 | if @props.forceOriginalDimensions
141 | @setState {
142 | width: @refs.img.clientWidth
143 | height: @refs.img.clientHeight
144 | }
145 |
146 | @setState imgLoaded: true
147 |
148 | # If the retina image check has already finished, set the 2x path.
149 | if @state?.retinaImgExists or not @props.checkIfRetinaImgExists
150 | @setState src: @getRetinaPath()
151 |
152 | getRetinaPath: ->
153 | if @state.srcIsArray
154 | return @props.src[1]
155 | else
156 | basename = path.basename(@props.src, path.extname(@props.src))
157 | basename = basename + @props.retinaImageSuffix + path.extname(@props.src)
158 | src = @props.src.replace(path.basename(@props.src), basename)
159 | return src
160 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | "webpack-dev-server/client?http://0.0.0.0:8080",
7 | './examples/index'
8 | ],
9 | devServer: {
10 | contentBase: './examples/'
11 | },
12 | devtool: "eval",
13 | debug: true,
14 | output: {
15 | path: path.join(__dirname, 'examples'),
16 | filename: 'bundle.js',
17 | },
18 | resolveLoader: {
19 | modulesDirectories: ['node_modules']
20 | },
21 | plugins: [
22 | new webpack.NoErrorsPlugin(),
23 | new webpack.IgnorePlugin(/un~$/)
24 | ],
25 | resolve: {
26 | extensions: ['', '.js', '.cjsx', '.coffee']
27 | },
28 | module: {
29 | loaders: [
30 | { test: /\.css$/, loaders: ['style', 'css']},
31 | { test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
32 | { test: /\.coffee$/, loader: 'coffee' }
33 | ]
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 |
5 | module.exports = {
6 | entry: [
7 | './examples/index'
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'examples'),
11 | filename: 'bundle.js',
12 | },
13 | resolveLoader: {
14 | modulesDirectories: ['node_modules']
15 | },
16 | plugins: [
17 | new webpack.DefinePlugin({
18 | "process.env": {
19 | NODE_ENV: JSON.stringify("production")
20 | }
21 | }),
22 | new webpack.optimize.DedupePlugin(),
23 | new webpack.optimize.UglifyJsPlugin()
24 | ],
25 | resolve: {
26 | extensions: ['', '.js', '.cjsx', '.coffee']
27 | },
28 | module: {
29 | loaders: [
30 | { test: /\.css$/, loaders: ['style', 'css']},
31 | { test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
32 | { test: /\.coffee$/, loader: 'coffee' }
33 | ]
34 | }
35 | };
36 |
--------------------------------------------------------------------------------