-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement responsiveness with srcSet and sizes #159
Changes from all commits
560385c
c03401a
0070943
1bb6808
9c504c9
ccdbfcf
d21c5ea
0392518
26b0035
88eb552
6438f15
7116c15
fac1757
df2db05
fa7860c
9cf4365
8322693
93a5dfb
6719d24
a7cbe47
7af8394
c4750f1
ecc0e84
78c25f0
8108460
4e53375
a6cc892
28b074a
9167095
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as CONSTANTS } from "./constants"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const CONSTANTS = {}; | ||
|
||
export default CONSTANTS; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,44 +4,20 @@ import ReactDOM from "react-dom"; | |
import React, { Component } from "react"; | ||
import PropTypes from "prop-types"; | ||
|
||
import processImage from "./support.js"; | ||
|
||
// Best way to include an img with an empty src https://stackoverflow.com/a/5775621/515634 and https://stackoverflow.com/a/19126281/515634 | ||
// Using '//:0' doesn't work in IE 11, but using a data-uri works. | ||
const EMPTY_IMAGE_SRC = | ||
"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="; | ||
import targetWidths from "./targetWidths"; | ||
import constructUrl from "./constructUrl"; | ||
|
||
const PACKAGE_VERSION = require("../package.json").version; | ||
|
||
const roundToNearest = (size, precision) => | ||
precision * Math.ceil(size / precision); | ||
const NODE_ENV = process.env.NODE_ENV; | ||
|
||
const isStringNotEmpty = str => | ||
str && typeof str === "string" && str.length > 0; | ||
const buildKey = idx => `react-imgix-${idx}`; | ||
|
||
const validTypes = ["bg", "img", "picture", "source"]; | ||
|
||
const defaultMap = { | ||
width: "defaultWidth", | ||
height: "defaultHeight" | ||
}; | ||
|
||
const findSizeForDimension = (dim, props = {}, state = {}) => { | ||
if (props[dim]) { | ||
return props[dim]; | ||
} else if (props.fluid && state[dim]) { | ||
return roundToNearest(state[dim], props.precision); | ||
} else if (props[defaultMap[dim]]) { | ||
return props[defaultMap[dim]]; | ||
} else { | ||
return 1; | ||
} | ||
}; | ||
const validTypes = ["img", "picture", "source"]; | ||
|
||
export default class ReactImgix extends Component { | ||
static propTypes = { | ||
aggressiveLoad: PropTypes.bool, | ||
auto: PropTypes.array, | ||
children: PropTypes.any, | ||
className: PropTypes.string, | ||
|
@@ -51,136 +27,158 @@ export default class ReactImgix extends Component { | |
entropy: PropTypes.bool, | ||
faces: PropTypes.bool, | ||
fit: PropTypes.string, | ||
fluid: PropTypes.bool, | ||
generateSrcSet: PropTypes.bool, | ||
disableSrcSet: PropTypes.bool, | ||
onMounted: PropTypes.func, | ||
sizes: PropTypes.string, | ||
src: PropTypes.string.isRequired, | ||
type: PropTypes.oneOf(validTypes), | ||
width: PropTypes.number, | ||
height: PropTypes.number, | ||
defaultHeight: PropTypes.number, | ||
defaultWidth: PropTypes.number, | ||
disableLibraryParam: PropTypes.bool | ||
}; | ||
static defaultProps = { | ||
aggressiveLoad: false, | ||
auto: ["format"], | ||
entropy: false, | ||
faces: true, | ||
fit: "crop", | ||
fluid: true, | ||
generateSrcSet: true, | ||
disableSrcSet: false, | ||
onMounted: () => {}, | ||
precision: 100, | ||
type: "img" | ||
}; | ||
state = { | ||
width: null, | ||
height: null, | ||
mounted: false | ||
}; | ||
|
||
forceLayout = () => { | ||
componentDidMount = () => { | ||
const node = ReactDOM.findDOMNode(this); | ||
this.setState({ | ||
width: node.scrollWidth, | ||
height: node.scrollHeight, | ||
mounted: true | ||
}); | ||
this.props.onMounted(node); | ||
}; | ||
|
||
componentDidMount = () => { | ||
this.forceLayout(); | ||
}; | ||
buildSrcs = () => { | ||
const props = this.props; | ||
const { | ||
width, | ||
height, | ||
entropy, | ||
faces, | ||
auto, | ||
customParams, | ||
disableLibraryParam, | ||
disableSrcSet, | ||
type | ||
} = props; | ||
|
||
let crop = false; | ||
if (faces) crop = "faces"; | ||
if (entropy) crop = "entropy"; | ||
if (props.crop) crop = props.crop; | ||
|
||
let fit = false; | ||
if (entropy || faces) fit = "crop"; | ||
if (props.fit) fit = props.fit; | ||
|
||
const fixedSize = width != null || height != null; | ||
|
||
const srcOptions = { | ||
auto, | ||
...customParams, | ||
crop, | ||
fit, | ||
...(disableLibraryParam ? {} : { ixlib: `react-${PACKAGE_VERSION}` }), | ||
...(fixedSize && height ? { height } : {}), | ||
...(fixedSize && width ? { width } : {}) | ||
}; | ||
|
||
_findSizeForDimension = dim => | ||
findSizeForDimension(dim, this.props, this.state); | ||
const src = constructUrl(this.props.src, srcOptions); | ||
|
||
let srcSet; | ||
|
||
if (disableSrcSet) { | ||
srcSet = src; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll be honest, it isn't very clear, by just reading this section of the code, what the actual behaviour is. I'll explain:
This is the actual behaviour. This value is not automatically applied to rendered element. For an Does this make more sense? Is there a way we can improve the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I'm on board now 👍 |
||
} else { | ||
if (fixedSize || type === "source") { | ||
const dpr2 = constructUrl(this.props.src, { ...srcOptions, dpr: 2 }); | ||
const dpr3 = constructUrl(this.props.src, { ...srcOptions, dpr: 3 }); | ||
srcSet = `${dpr2} 2x, ${dpr3} 3x`; | ||
} else { | ||
const buildSrcSetPair = targetWidth => { | ||
const url = constructUrl(this.props.src, { | ||
...srcOptions, | ||
width: targetWidth | ||
}); | ||
return `${url} ${targetWidth}w`; | ||
}; | ||
const addFallbackSrc = srcSet => srcSet.concat(src); | ||
srcSet = addFallbackSrc(targetWidths.map(buildSrcSetPair)).join(", "); | ||
} | ||
} | ||
|
||
return { | ||
src, | ||
srcSet | ||
}; | ||
}; | ||
|
||
render() { | ||
const { | ||
aggressiveLoad, | ||
auto, | ||
bg, | ||
children, | ||
component, | ||
customParams, | ||
crop, | ||
entropy, | ||
faces, | ||
fit, | ||
generateSrcSet, | ||
src, | ||
disableSrcSet, | ||
type, | ||
...other | ||
width, | ||
height | ||
} = this.props; | ||
let _src = EMPTY_IMAGE_SRC; | ||
let srcSet = null; | ||
let _component = component; | ||
|
||
let width = this._findSizeForDimension("width"); | ||
let height = this._findSizeForDimension("height"); | ||
|
||
let _crop = false; | ||
if (faces) _crop = "faces"; | ||
if (entropy) _crop = "entropy"; | ||
if (crop) _crop = crop; | ||
// Pre-render checks | ||
if (NODE_ENV !== "production") { | ||
if ( | ||
type === "img" && | ||
width == null && | ||
height == null && | ||
this.props.sizes == null && | ||
!this.props._inPicture | ||
) { | ||
console.warn( | ||
"If width and height are not set, a sizes attribute should be passed." | ||
); | ||
} | ||
} | ||
|
||
let _fit = false; | ||
if (entropy) _fit = "crop"; | ||
if (fit) _fit = fit; | ||
let _component = component; | ||
const imgProps = this.props.imgProps || {}; | ||
|
||
let _children = children; | ||
|
||
if (this.state.mounted || aggressiveLoad) { | ||
const srcOptions = { | ||
auto: auto, | ||
...customParams, | ||
crop: _crop, | ||
fit: _fit, | ||
width, | ||
height, | ||
...(this.props.disableLibraryParam | ||
? {} | ||
: { ixlib: `react-${PACKAGE_VERSION}` }) | ||
}; | ||
|
||
_src = processImage(src, srcOptions); | ||
const dpr2 = processImage(src, { ...srcOptions, dpr: 2 }); | ||
const dpr3 = processImage(src, { ...srcOptions, dpr: 3 }); | ||
srcSet = `${dpr2} 2x, ${dpr3} 3x`; | ||
} | ||
|
||
let _alt = (this.props.imgProps || {}).alt; | ||
const { src, srcSet } = this.buildSrcs(); | ||
|
||
let childProps = { | ||
...this.props.imgProps, | ||
sizes: this.props.sizes, | ||
className: this.props.className, | ||
width: other.width <= 1 ? null : other.width, | ||
height: other.height <= 1 ? null : other.height, | ||
alt: this.state.mounted || aggressiveLoad ? _alt : undefined | ||
width: width <= 1 ? null : width, | ||
height: height <= 1 ? null : height, | ||
alt: imgProps.alt | ||
}; | ||
|
||
switch (type) { | ||
case "bg": | ||
if (!component) { | ||
_component = "div"; | ||
} | ||
childProps.style = { | ||
backgroundSize: "cover", | ||
backgroundImage: isStringNotEmpty(_src) ? `url('${_src}')` : null, | ||
...childProps.style | ||
}; | ||
// TODO: Remove in v9 | ||
throw new Error( | ||
`type='bg' has been removed in this version of react-imgix. If you would like this re-implemented please give this issues a thumbs up: https://github.com/imgix/react-imgix/issues/160` | ||
); | ||
break; | ||
case "img": | ||
if (!component) { | ||
_component = "img"; | ||
} | ||
|
||
if (generateSrcSet) { | ||
if (!disableSrcSet) { | ||
childProps.srcSet = srcSet; | ||
} | ||
childProps.src = _src; | ||
childProps.src = src; | ||
break; | ||
case "source": | ||
if (!component) { | ||
|
@@ -189,14 +187,15 @@ export default class ReactImgix extends Component { | |
|
||
// strip out the "alt" tag from childProps since it's not allowed | ||
delete childProps.alt; | ||
delete childProps.src; | ||
|
||
// inside of a <picture> element a <source> element ignores its src | ||
// attribute in favor of srcSet so we set that with either an actual | ||
// srcSet or a single src | ||
if (generateSrcSet) { | ||
childProps.srcSet = `${_src}, ${srcSet}`; | ||
if (disableSrcSet) { | ||
childProps.srcSet = src; | ||
} else { | ||
childProps.srcSet = _src; | ||
childProps.srcSet = `${src}, ${srcSet}`; | ||
} | ||
// for now we'll take media from imgProps which isn't ideal because | ||
// a) this isn't an <img> | ||
|
@@ -220,7 +219,10 @@ export default class ReactImgix extends Component { | |
// make sure all of our children have key set, otherwise we get react warnings | ||
_children = | ||
React.Children.map(children, (child, idx) => | ||
React.cloneElement(child, { key: buildKey(idx) }) | ||
React.cloneElement(child, { | ||
key: buildKey(idx), | ||
_inPicture: true | ||
}) | ||
) || []; | ||
|
||
// look for an <img> or <ReactImgix type='img'> - at the bare minimum we | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. How does this end up manifesting when this code is run in-browser?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an extremely common pattern which is used everywhere in React. It is usually defined by webpack plugin or webpack itself.