Skip to content

Commit a31cd7e

Browse files
author
Amir Tocker
committed
Add debounce to responsive functionality.
1 parent 75ffd09 commit a31cd7e

File tree

6 files changed

+93
-97
lines changed

6 files changed

+93
-97
lines changed

src/components/CloudinaryComponent/CloudinaryComponent.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export default class CloudinaryComponent extends Component {
6868
* @param options one or more options objects
6969
*/
7070
static normalizeOptions(...options) {
71-
var result = {};
7271
return options.reduce((left, right)=> {
7372
for (let key in right) {
7473
let value = right[key];
@@ -84,7 +83,6 @@ export default class CloudinaryComponent extends Component {
8483
getUrl(options) {
8584
let transformation = this.getTransformation(options);
8685
let cl = Cloudinary.new(options);
87-
console.log("options: ", options, "transformation:", transformation);
8886
return cl.url(options.public_id, transformation);
8987
}
9088

src/components/Image/Image.js

Lines changed: 51 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,25 @@
11
import React, {Component, PropTypes} from 'react';
22
import cloudinary, {Util} from 'cloudinary-core';
33
import CloudinaryComponent from '../CloudinaryComponent';
4-
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-
}
4+
import debounce from '../../util/debounce';
5+
import firstDefined from '../../util/firstDefined';
246

257
export default class Image extends CloudinaryComponent {
268
constructor(props, context) {
279
super(props, context);
28-
console.log("Image constructor");
2910
let options = CloudinaryComponent.normalizeOptions(context, props);
30-
let url = this.getUrl(options);
3111
this.handleResize = this.handleResize.bind(this);
3212

3313
let state = {responsive: false, url: "", breakpoints: defaultBreakpoints};
3414
this.state = Object.assign(state, this.prepareState(props, context));
3515
this.state = state;
36-
console.log(this);
3716
}
3817

3918
get window() {
4019
return (this.element && this.element.ownerDocument) ? (this.element.ownerDocument.defaultView || window) : window;
4120
}
4221

4322
componentWillReceiveProps(nextProps, nextContext) {
44-
console.log("componentWillReceiveProps",nextProps, nextContext);
4523
let state = this.prepareState(nextProps, nextContext);
4624
this.setState(state);
4725
}
@@ -50,80 +28,80 @@ export default class Image extends CloudinaryComponent {
5028
let options = CloudinaryComponent.normalizeOptions(context, props);
5129
let url = this.getUrl(options);
5230
let state = {};
53-
console.log("prepareState options ", options);
5431
if (options.breakpoints !== undefined) {
5532
state.breakpoints = options.breakpoints;
5633
}
5734
if (options.responsive) {
58-
console.log("prepareState - responsive");
5935
state.responsive = true;
6036
url = this.cloudinary_update(url, state);
6137
}
6238

6339
let currentState = this.state || {};
6440
if (/* FIXME probably not needed */ Util.isEmpty(currentState.url) || url !== currentState.url) {
65-
console.log("prepareState setting url ", url);
6641
state.url = url;
6742
}
6843
return state;
6944
}
7045

7146
componentWillMount() {
72-
console.log("componentWillMount");
7347
super.componentWillMount();
7448
}
7549

7650
componentDidMount() {
77-
console.log("componentDidMount");
7851
super.componentDidMount();
7952
// now that we have a this.element, we need to calculate the URL
8053
let state = this.prepareState();
81-
if(state.url !== undefined){
82-
console.log("componentDidMount setting state");
54+
if (state.url !== undefined) {
8355
this.setState(state);
8456
}
8557
}
8658

8759
componentWillUnmount() {
88-
console.log("componentWillUnmount");
89-
this.window.removeEventListener('resize', this.handleResize);
60+
this.element = undefined;
61+
if (this.listener) {
62+
this.listener.cancel();
63+
this.window.removeEventListener('resize', this.listener);
64+
}
65+
this.listener = undefined;
9066
}
9167

9268
componentWillUpdate(nextProps, nextState, nextContext) {
9369
// 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);
70+
if (nextState.responsive) {
71+
const wait = firstDefined(nextProps.responsiveDebounce, nextContext.responsiveDebounce, 100);
72+
if (this.listener) {
73+
this.window.removeEventListener('resize', this.listener);
74+
}
75+
this.listener = debounce(this.handleResize, wait);
76+
this.window.addEventListener('resize', this.listener);
9877
}
99-
10078
}
10179

10280
componentDidUpdate(prevProps, prevState, prevContext) {
103-
console.log("componentDidUpdate");
10481
}
10582

10683
shouldComponentUpdate(nextProps, nextState, nextContext) {
107-
console.log("shouldComponentUpdate");
10884
return true;
10985
}
11086

111-
handleResize(e){
87+
handleResize(e) {
11288
let options = CloudinaryComponent.normalizeOptions(this.context, this.props);
11389
let url = this.getUrl(options);
114-
if(this.state.responsive){
90+
if (this.state.responsive) {
11591
url = this.cloudinary_update(url);
11692
let partialState = {url: url};
117-
this.setState(partialState);
93+
if (this.element) {
94+
this.setState(partialState);
95+
}
11896
}
11997
}
12098

12199
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);
100+
var {public_id, responsive, responsive_debounce, children, ...options} = CloudinaryComponent.normalizeOptions(this.props,
101+
this.context);
124102
var attributes = cloudinary.Transformation.new(options).toHtmlAttributes();
125103
return (
126-
<img {...attributes} src={this.state.url} ref={(e)=>{this.element = e;}}/>
104+
<img {...attributes} src={this.state.url} ref={(e)=> {this.element = e;}}/>
127105
);
128106
}
129107

@@ -139,13 +117,11 @@ export default class Image extends CloudinaryComponent {
139117
containerWidth = Util.width(element);
140118
}
141119
}
142-
console.log("findContainerWidth returning ", containerWidth);
143120
return containerWidth;
144121
};
145122

146123
applyBreakpoints(tag, width, steps, options) {
147-
console.log("applyBreakpoints");
148-
var ref, ref1, ref2, responsive_use_breakpoints;
124+
var responsive_use_breakpoints;
149125
options = CloudinaryComponent.normalizeOptions(this.context, this.props, options);
150126
responsive_use_breakpoints = options.responsiveUseBreakpoints;
151127
if ((!responsive_use_breakpoints) || (responsive_use_breakpoints === 'resize' && !options.resizing)) {
@@ -156,14 +132,13 @@ export default class Image extends CloudinaryComponent {
156132
};
157133

158134
calc_breakpoint(element, width, steps) {
159-
console.log("calc_breakpoint");
160135
var breakpoints, point;
161136
breakpoints = this.state.breakpoints || defaultBreakpoints;
162137
if (Util.isFunction(breakpoints)) {
163138
return breakpoints(width, steps);
164139
} else {
165140
if (Util.isString(breakpoints)) {
166-
breakpoints = ((function() {
141+
breakpoints = ((function () {
167142
var j, len, ref, results;
168143
ref = breakpoints.split(',');
169144
results = [];
@@ -172,7 +147,7 @@ export default class Image extends CloudinaryComponent {
172147
results.push(parseInt(point));
173148
}
174149
return results;
175-
})()).sort(function(a, b) {
150+
})()).sort(function (a, b) {
176151
return a - b;
177152
});
178153
}
@@ -195,8 +170,8 @@ export default class Image extends CloudinaryComponent {
195170
}
196171
return dprString;
197172
};
173+
198174
updateDpr(dataSrc, roundDpr) {
199-
console.log("updateDpr");
200175
return dataSrc.replace(/\bdpr_(1\.0|auto)\b/g, 'dpr_' + this.device_pixel_ratio(roundDpr));
201176
};
202177

@@ -205,7 +180,7 @@ export default class Image extends CloudinaryComponent {
205180
imageWidth = this.state.width || 0;
206181
if (requiredWidth > imageWidth) {
207182
imageWidth = requiredWidth;
208-
this.setState( {width: requiredWidth});
183+
this.setState({width: requiredWidth});
209184
}
210185
return imageWidth;
211186
};
@@ -214,35 +189,40 @@ export default class Image extends CloudinaryComponent {
214189
var requiredWidth;
215190
var match;
216191
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");
192+
if (options.responsive || this.state && this.state.responsive) {
221193
let containerWidth = this.findContainerWidth();
222194
if (containerWidth !== 0) {
223195
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);
196+
requiredWidth = this.maxWidth(containerWidth, this.element);
197+
resultUrl = resultUrl.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/,
198+
"w_auto:breakpoints$1:" + requiredWidth);
226199
} 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);
200+
requiredWidth = this.applyBreakpoints(this.element, containerWidth, match[2], options);
201+
requiredWidth = this.maxWidth(requiredWidth, this.element);
202+
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
230203
}
231-
// Util.removeAttribute(this.element, 'width');
232-
// if (!options.responsive_preserve_height) {
233-
// Util.removeAttribute(this.element, 'height');
234-
// }
235204
} else {
236205
resultUrl = "";
237206
}
238-
239207
}
240-
console.log("cloudinary_update returning", resultUrl);
241-
console.groupEnd();
242208
return resultUrl;
243209
}
244210
}
245211

246212
Image.defaultProps = {};
247213
Image.contextTypes = CloudinaryComponent.contextTypes;
248-
Image.propTypes = CloudinaryComponent.propTypes;
214+
Image.propTypes = CloudinaryComponent.propTypes;
215+
216+
217+
function defaultBreakpoints(width, steps = 100) {
218+
return steps * Math.ceil(width / steps);
219+
}
220+
221+
function closestAbove(list, value) {
222+
var i;
223+
i = list.length - 2;
224+
while (i >= 0 && list[i] >= value) {
225+
i--;
226+
}
227+
return list[i + 1];
228+
}

src/components/Transformation/Transformation.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,6 @@ export default class Transformation extends CloudinaryComponent {
66
super(props);
77
}
88

9-
componentWillReceiveProps(nextProps) {
10-
}
11-
12-
componentWillMount() {
13-
}
14-
15-
componentDidMount() {
16-
}
17-
18-
componentWillUnmount() {
19-
}
20-
21-
componentWillUpdate(nextProps, nextState) {
22-
}
23-
24-
componentDidUpdate(prevProps, prevState) {
25-
}
26-
279
shouldComponentUpdate(nextProps, nextState) {
2810
return true;
2911
}

src/util/debounce.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export default function debounce(func, wait, immediate) {
2+
let timeout = null;
3+
let debounced = () => {
4+
const context = this;
5+
const args = arguments;
6+
const callNow = immediate && !timeout;
7+
if(timeout){
8+
clearTimeout(timeout);
9+
}
10+
const later = () => {
11+
timeout = null;
12+
if (!immediate) {
13+
func.apply(context, args);
14+
}
15+
};
16+
17+
timeout = setTimeout(later, wait);
18+
if (callNow) {
19+
func.apply(context, args);
20+
}
21+
};
22+
23+
debounced.cancel = ()=>{
24+
clearTimeout(timeout);
25+
timeout = null;
26+
};
27+
return debounced;
28+
29+
}

src/util/firstDefined.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default function firstDefined(...values){
2+
for(let value of values) {
3+
if(value !== undefined) return value;
4+
}
5+
return undefined;
6+
}

stories/index.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ import CloudinaryContext from '../src/components/CloudinaryContext';
77
import cloudinary from 'cloudinary-core';
88

99
storiesOf('Image', module).addWithInfo('image', "Basic tag", ()=> {
10-
// let t = {width: 0.5, crop: "scale"};
11-
let t = {crop: "scale"};
1210
return (
13-
<Image cloudName="demo" publicId="sample" crop="scale"/>
11+
<Image cloudName="demo" publicId="sample"/>
1412
)
1513
}
1614
).addWithInfo('responsive image', "Basic tag", ()=> {
1715
return (
1816
<Image cloudName="demo" publicId="sample" crop="scale" width="auto" responsive/>
1917
)
2018
}
19+
).addWithInfo('responsive image with very slow debounce', "Basic tag", ()=> {
20+
return (
21+
<Image cloudName="demo" publicId="sample" crop="scale" width="auto" responsive responsiveDebounce="2000"/>
22+
)
23+
}
2124
).addWithInfo('image with alt', "Demostrate using an img tag attribute", ()=> {
2225
let t = {width: 0.5, crop: "scale"};
2326
return (
@@ -32,11 +35,10 @@ storiesOf('Image', module).addWithInfo('image', "Basic tag", ()=> {
3235
).addWithInfo('image with style', 'image with style', ()=> {
3336
let t = {width: 0.5, crop: "scale"};
3437
return (
35-
<Image cloudName="demo" publicId="sample" style={{border: "20px solid"}}/>
38+
<Image {...t} cloudName="demo" publicId="sample" style={{border: "20px solid"}}/>
3639
)
3740
}
3841
).addWithInfo('image with chained transformation', 'image with chained transformation', ()=> {
39-
let t = {width: 0.5, crop: "scale"};
4042
return (
4143
<div>
4244
<Image cloudName="demo" publicId="sample" width="100" crop="scale" angle="10"
@@ -46,7 +48,6 @@ storiesOf('Image', module).addWithInfo('image', "Basic tag", ()=> {
4648
)
4749
}
4850
).addWithInfo('image with nested chained transformation', 'image with chained transformation', ()=> {
49-
let t = {width: 0.5, crop: "scale"};
5051
return (
5152
<div>
5253
<Image cloudName="demo" publicId="sample">

0 commit comments

Comments
 (0)