Skip to content

Commit 75ffd09

Browse files
author
Amir Tocker
committed
Handle responsive images.
1 parent 7483e8b commit 75ffd09

File tree

3 files changed

+208
-45
lines changed

3 files changed

+208
-45
lines changed

src/components/CloudinaryComponent/CloudinaryComponent.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ export default class CloudinaryComponent extends Component {
5050
});
5151
}
5252

53-
getTransformation(props = this.props , context = this.context) {
54-
let options = CloudinaryComponent.normalizeOptions(context, props );
55-
if(this.props.children !== undefined){
53+
getTransformation(options) {
54+
var transformation;
55+
if (this.props.children !== undefined) {
5656
let childrenOptions = this.getChildTransformations(this.props.children);
5757
if (!Util.isEmpty(childrenOptions)) {
58-
options.transformation = childrenOptions;
58+
transformation = childrenOptions;
5959
}
6060
}
61-
return options;
61+
return {...options, transformation};
6262
}
6363

6464
/**
@@ -81,12 +81,11 @@ export default class CloudinaryComponent extends Component {
8181
, {});
8282
}
8383

84-
getUrl(props = this.props , context = this.context) {
85-
let options = CloudinaryComponent.normalizeOptions(context, props);
86-
let transformation = this.getTransformation(props, context);
84+
getUrl(options) {
85+
let transformation = this.getTransformation(options);
8786
let cl = Cloudinary.new(options);
8887
console.log("options: ", options, "transformation:", transformation);
89-
return cl.url(props.publicId, transformation);
88+
return cl.url(options.public_id, transformation);
9089
}
9190

9291
}
@@ -95,6 +94,7 @@ CloudinaryComponent.contextTypes = typesFrom(CloudinaryComponent.VALID_OPTIONS);
9594

9695
CloudinaryComponent.propTypes = CloudinaryComponent.contextTypes;
9796
CloudinaryComponent.propTypes.publicId = PropTypes.string;
97+
CloudinaryComponent.propTypes.responsive = PropTypes.bool;
9898

9999
CloudinaryComponent.childContextTypes = {};
100100

src/components/Image/Image.js

Lines changed: 194 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,86 +2,244 @@ import React, {Component, PropTypes} from 'react';
22
import cloudinary, {Util} from 'cloudinary-core';
33
import CloudinaryComponent from '../CloudinaryComponent';
44

5+
function firstDefined(...values){
6+
for(let value of values) {
7+
if(value !== undefined) return value;
8+
}
9+
return undefined;
10+
}
11+
12+
function defaultBreakpoints(width, steps = 100) {
13+
return steps * Math.ceil(width / steps);
14+
}
15+
16+
function closestAbove(list, value) {
17+
var i;
18+
i = list.length - 2;
19+
while (i >= 0 && list[i] >= value) {
20+
i--;
21+
}
22+
return list[i + 1];
23+
}
24+
525
export default class Image extends CloudinaryComponent {
626
constructor(props, context) {
727
super(props, context);
8-
let url = this.getUrl(props, context);
28+
console.log("Image constructor");
29+
let options = CloudinaryComponent.normalizeOptions(context, props);
30+
let url = this.getUrl(options);
931
this.handleResize = this.handleResize.bind(this);
10-
this.state = {url};
32+
33+
let state = {responsive: false, url: "", breakpoints: defaultBreakpoints};
34+
this.state = Object.assign(state, this.prepareState(props, context));
35+
this.state = state;
36+
console.log(this);
37+
}
38+
39+
get window() {
40+
return (this.element && this.element.ownerDocument) ? (this.element.ownerDocument.defaultView || window) : window;
1141
}
1242

1343
componentWillReceiveProps(nextProps, nextContext) {
14-
let url = this.getUrl(nextProps, nextContext);
15-
if(url !== this.state.url){
16-
console.log("setting url ", url);
17-
this.setState({url, width: 0});
44+
console.log("componentWillReceiveProps",nextProps, nextContext);
45+
let state = this.prepareState(nextProps, nextContext);
46+
this.setState(state);
47+
}
48+
49+
prepareState(props = this.props, context = this.context) {
50+
let options = CloudinaryComponent.normalizeOptions(context, props);
51+
let url = this.getUrl(options);
52+
let state = {};
53+
console.log("prepareState options ", options);
54+
if (options.breakpoints !== undefined) {
55+
state.breakpoints = options.breakpoints;
56+
}
57+
if (options.responsive) {
58+
console.log("prepareState - responsive");
59+
state.responsive = true;
60+
url = this.cloudinary_update(url, state);
1861
}
62+
63+
let currentState = this.state || {};
64+
if (/* FIXME probably not needed */ Util.isEmpty(currentState.url) || url !== currentState.url) {
65+
console.log("prepareState setting url ", url);
66+
state.url = url;
67+
}
68+
return state;
1969
}
2070

2171
componentWillMount() {
22-
window.addEventListener('resize', this.handleResize);
72+
console.log("componentWillMount");
73+
super.componentWillMount();
2374
}
2475

2576
componentDidMount() {
77+
console.log("componentDidMount");
78+
super.componentDidMount();
79+
// now that we have a this.element, we need to calculate the URL
80+
let state = this.prepareState();
81+
if(state.url !== undefined){
82+
console.log("componentDidMount setting state");
83+
this.setState(state);
84+
}
2685
}
2786

2887
componentWillUnmount() {
29-
window.removeEventListener('resize', this.handleResize);
30-
88+
console.log("componentWillUnmount");
89+
this.window.removeEventListener('resize', this.handleResize);
3190
}
3291

3392
componentWillUpdate(nextProps, nextState, nextContext) {
93+
// TODO check responsive. also check for responsive state change.
94+
console.log("componentWillUpdate", nextState);
95+
if(nextState.responsive){
96+
console.log("componentWillUpdate - setting listener");
97+
this.window.addEventListener('resize', this.handleResize);
98+
}
3499

35100
}
36101

37102
componentDidUpdate(prevProps, prevState, prevContext) {
103+
console.log("componentDidUpdate");
38104
}
39105

40106
shouldComponentUpdate(nextProps, nextState, nextContext) {
107+
console.log("shouldComponentUpdate");
41108
return true;
42109
}
43110

44-
getUrl(props, context, width){
45-
console.log("getUrl", this);
46-
width = width || (this.state ? this.state.width: undefined);
47-
console.log("width is ", width);
48-
if(width){
49-
console.log("Width is ", width);
50-
props = Object.assign({width}, props);
51-
}
52-
console.log(props);
53-
return super.getUrl(props, context);
54-
55-
}
56111
handleResize(e){
57-
const width = this.findContainerWidth(this.element);
58-
if(!width) {
59-
return;
112+
let options = CloudinaryComponent.normalizeOptions(this.context, this.props);
113+
let url = this.getUrl(options);
114+
if(this.state.responsive){
115+
url = this.cloudinary_update(url);
116+
let partialState = {url: url};
117+
this.setState(partialState);
60118
}
61-
let partialState = {width, url: this.getUrl(this.props, this.context, width)};
62-
this.setState(partialState);
63119
}
64120

65-
findContainerWidth(element) {
66-
console.log("findContainerWidth");
121+
render() {
122+
var {public_id, responsive, children, ...options} = CloudinaryComponent.normalizeOptions(this.props, this.context);
123+
console.log("render image", this._reactInternalInstance._debugID, this.state.url, this.state.width);
124+
var attributes = cloudinary.Transformation.new(options).toHtmlAttributes();
125+
return (
126+
<img {...attributes} src={this.state.url} ref={(e)=>{this.element = e;}}/>
127+
);
128+
}
129+
130+
// methods from cloudinary_js
131+
132+
findContainerWidth() {
67133
var containerWidth, style;
68134
containerWidth = 0;
135+
let element = this.element;
69136
while (((element = element != null ? element.parentNode : void 0) instanceof Element) && !containerWidth) {
70-
style = window.getComputedStyle(element);
137+
style = this.window.getComputedStyle(element);
71138
if (!/^inline/.test(style.display)) {
72139
containerWidth = Util.width(element);
73140
}
74141
}
142+
console.log("findContainerWidth returning ", containerWidth);
75143
return containerWidth;
76144
};
77145

78-
render() {
79-
var {public_id, children, ...options} = CloudinaryComponent.normalizeOptions(this.props, this.context);
80-
console.log("render image", this.state.url, this.state.width);
81-
var attributes = cloudinary.Transformation.new(options).toHtmlAttributes();
82-
return (
83-
<img {...attributes} src={this.state.url} ref={(e)=>{this.element = e;}}/>
84-
);
146+
applyBreakpoints(tag, width, steps, options) {
147+
console.log("applyBreakpoints");
148+
var ref, ref1, ref2, responsive_use_breakpoints;
149+
options = CloudinaryComponent.normalizeOptions(this.context, this.props, options);
150+
responsive_use_breakpoints = options.responsiveUseBreakpoints;
151+
if ((!responsive_use_breakpoints) || (responsive_use_breakpoints === 'resize' && !options.resizing)) {
152+
return width;
153+
} else {
154+
return this.calc_breakpoint(tag, width, steps);
155+
}
156+
};
157+
158+
calc_breakpoint(element, width, steps) {
159+
console.log("calc_breakpoint");
160+
var breakpoints, point;
161+
breakpoints = this.state.breakpoints || defaultBreakpoints;
162+
if (Util.isFunction(breakpoints)) {
163+
return breakpoints(width, steps);
164+
} else {
165+
if (Util.isString(breakpoints)) {
166+
breakpoints = ((function() {
167+
var j, len, ref, results;
168+
ref = breakpoints.split(',');
169+
results = [];
170+
for (j = 0, len = ref.length; j < len; j++) {
171+
point = ref[j];
172+
results.push(parseInt(point));
173+
}
174+
return results;
175+
})()).sort(function(a, b) {
176+
return a - b;
177+
});
178+
}
179+
return closestAbove(breakpoints, width);
180+
}
181+
};
182+
183+
device_pixel_ratio(roundDpr = true) {
184+
var dpr, dprString;
185+
dpr = (typeof this.window !== "undefined" && this.window !== null ? this.window.devicePixelRatio : void 0) || 1;
186+
if (roundDpr) {
187+
dpr = Math.ceil(dpr);
188+
}
189+
if (dpr <= 0 || isNaN(dpr)) {
190+
dpr = 1;
191+
}
192+
dprString = dpr.toString();
193+
if (dprString.match(/^\d+$/)) {
194+
dprString += '.0';
195+
}
196+
return dprString;
197+
};
198+
updateDpr(dataSrc, roundDpr) {
199+
console.log("updateDpr");
200+
return dataSrc.replace(/\bdpr_(1\.0|auto)\b/g, 'dpr_' + this.device_pixel_ratio(roundDpr));
201+
};
202+
203+
maxWidth(requiredWidth) {
204+
var imageWidth;
205+
imageWidth = this.state.width || 0;
206+
if (requiredWidth > imageWidth) {
207+
imageWidth = requiredWidth;
208+
this.setState( {width: requiredWidth});
209+
}
210+
return imageWidth;
211+
};
212+
213+
cloudinary_update(url, options = {}) {
214+
var requiredWidth;
215+
var match;
216+
let resultUrl = this.updateDpr(url, options.roundDpr);
217+
console.group("cloudinary_update", resultUrl);
218+
console.log("state", this.state);
219+
if(options.responsive || this.state && this.state.responsive) {
220+
console.log("responsive");
221+
let containerWidth = this.findContainerWidth();
222+
if (containerWidth !== 0) {
223+
if (/w_auto:breakpoints/.test(resultUrl)) {
224+
requiredWidth = this.maxWidth(containerWidth, this.element);
225+
resultUrl = resultUrl.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/, "w_auto:breakpoints$1:" + requiredWidth);
226+
} else if (match = /w_auto(:(\d+))?/.exec(resultUrl)) {
227+
requiredWidth = this.applyBreakpoints(this.element, containerWidth, match[2], options);
228+
requiredWidth = this.maxWidth(requiredWidth, this.element);
229+
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
230+
}
231+
// Util.removeAttribute(this.element, 'width');
232+
// if (!options.responsive_preserve_height) {
233+
// Util.removeAttribute(this.element, 'height');
234+
// }
235+
} else {
236+
resultUrl = "";
237+
}
238+
239+
}
240+
console.log("cloudinary_update returning", resultUrl);
241+
console.groupEnd();
242+
return resultUrl;
85243
}
86244
}
87245

stories/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ storiesOf('Image', module).addWithInfo('image', "Basic tag", ()=> {
1313
<Image cloudName="demo" publicId="sample" crop="scale"/>
1414
)
1515
}
16+
).addWithInfo('responsive image', "Basic tag", ()=> {
17+
return (
18+
<Image cloudName="demo" publicId="sample" crop="scale" width="auto" responsive/>
19+
)
20+
}
1621
).addWithInfo('image with alt', "Demostrate using an img tag attribute", ()=> {
1722
let t = {width: 0.5, crop: "scale"};
1823
return (

0 commit comments

Comments
 (0)