Skip to content

Commit 784f610

Browse files
committed
Add CSS effects when the image becomes visible
1 parent 662389b commit 784f610

File tree

7 files changed

+150
-25
lines changed

7 files changed

+150
-25
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"babel-plugin-transform-object-rest-spread": "^6.26.0",
1717
"babel-preset-env": "^1.6.1",
1818
"babel-preset-react": "^6.24.1",
19+
"css-loader": "^0.28.10",
1920
"enzyme": "^3.3.0",
2021
"enzyme-adapter-react-16": "^1.1.1",
2122
"eslint": "^4.18.1",
@@ -26,6 +27,7 @@
2627
"path": "^0.12.7",
2728
"react": "^16.2.0",
2829
"react-dom": "^16.2.0",
30+
"style-loader": "^0.20.2",
2931
"webpack": "^3.11.0"
3032
},
3133
"scripts": {

src/components/LazyLoadImage.jsx

+73-22
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,102 @@ import LazyLoadComponent from './LazyLoadComponent.jsx';
66
class LazyLoadImage extends React.Component {
77
constructor(props) {
88
super(props);
9+
10+
this.state = {
11+
loaded: false
12+
};
913
}
1014

11-
render() {
12-
const { afterLoad, beforeLoad, delayMethod, delayTime, placeholder,
13-
placeholderSrc, scrollPosition, threshold, visibleByDefault,
14-
...imgProps } = this.props;
15+
onImageLoad() {
16+
return () => {
17+
this.props.afterLoad();
18+
19+
this.setState({
20+
loaded: true
21+
});
22+
};
23+
}
24+
25+
getImg() {
26+
const { afterLoad, beforeLoad, delayMethod, delayTime, effect,
27+
placeholder, placeholderSrc, scrollPosition, threshold,
28+
visibleByDefault, ...imgProps } = this.props;
1529

16-
const lazyLoadComponent = (
30+
return <img onLoad={this.onImageLoad()} {...imgProps} />;
31+
}
32+
33+
getLazyLoadImage(image) {
34+
const { beforeLoad, className, delayMethod, delayTime,
35+
height, placeholder, scrollPosition, style, threshold,
36+
visibleByDefault, width } = this.props;
37+
38+
return (
1739
<LazyLoadComponent
1840
beforeLoad={beforeLoad}
19-
className={this.props.className}
41+
className={className}
2042
delayMethod={delayMethod}
2143
delayTime={delayTime}
22-
height={this.props.height}
44+
height={height}
2345
placeholder={placeholder}
2446
scrollPosition={scrollPosition}
25-
style={this.props.style}
47+
style={style}
2648
threshold={threshold}
2749
visibleByDefault={visibleByDefault}
28-
width={this.props.width}>
29-
<img onLoad={afterLoad} {...imgProps} />
50+
width={width}>
51+
{image}
3052
</LazyLoadComponent>
3153
);
54+
}
3255

33-
if (!placeholderSrc || visibleByDefault) {
34-
return lazyLoadComponent;
35-
}
56+
getWrappedLazyLoadImage(lazyLoadImage) {
57+
const { effect, height, placeholderSrc, width } = this.props;
58+
const { loaded } = this.state;
59+
60+
const loadedClassName = loaded ?
61+
' lazy-load-image-loaded' :
62+
'';
3663

3764
return (
38-
<span style={{
39-
backgroundImage: 'url( ' + placeholderSrc + ')',
40-
backgroundSize: '100% 100%',
41-
color: 'transparent',
42-
display: 'inline-block',
43-
height: this.props.height,
44-
width: this.props.width
45-
}}>
46-
{lazyLoadComponent}
65+
<span
66+
className={'lazy-load-image-background ' + effect + loadedClassName}
67+
style={{
68+
backgroundImage: 'url( ' + placeholderSrc + ')',
69+
backgroundSize: '100% 100%',
70+
color: 'transparent',
71+
display: 'inline-block',
72+
height: height,
73+
width: width
74+
}}>
75+
{lazyLoadImage}
4776
</span>
4877
);
4978
}
79+
80+
render() {
81+
const { effect, placeholderSrc, visibleByDefault } = this.props;
82+
const { loaded } = this.state;
83+
84+
const image = this.getImg();
85+
const lazyLoadImage = loaded ?
86+
image : this.getLazyLoadImage(image);
87+
88+
if ((!effect && !placeholderSrc) || visibleByDefault) {
89+
return lazyLoadImage;
90+
}
91+
92+
return this.getWrappedLazyLoadImage(lazyLoadImage);
93+
}
5094
}
5195

5296
LazyLoadImage.propTypes = {
97+
afterLoad: PropTypes.func,
98+
effect: PropTypes.string,
5399
placeholderSrc: PropTypes.string
54100
};
55101

102+
LazyLoadImage.defaultProps = {
103+
afterLoad: () => ({}),
104+
effect: ''
105+
};
106+
56107
export default LazyLoadImage;

src/components/LazyLoadImage.spec.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ configure({ adapter: new Adapter() });
1111
const {
1212
findRenderedComponentWithType,
1313
findRenderedDOMComponentWithTag,
14+
scryRenderedDOMComponentsWithClass,
1415
scryRenderedDOMComponentsWithTag,
1516
Simulate
1617
} = ReactTestUtils;
@@ -52,20 +53,44 @@ describe('LazyLoadImage', function() {
5253
expect(img.src).toEqual(props.src);
5354
});
5455

55-
it('calls afterLoad when img triggers onLoad', function() {
56+
it('updates state and calls afterLoad when img triggers onLoad', function() {
5657
const afterLoad = jest.fn();
5758
const lazyLoadImage = mount(
58-
<LazyLoadImage
59-
afterLoad={afterLoad} />
59+
<LazyLoadImage afterLoad={afterLoad} />
6060
);
6161

6262
const img = findRenderedDOMComponentWithTag(lazyLoadImage.instance(), 'img');
6363

6464
Simulate.load(img);
6565

66+
expect(lazyLoadImage.instance().state.loaded);
6667
expect(afterLoad).toHaveBeenCalledTimes(1);
6768
});
6869

70+
it('sets loaded class to wrapper when img triggers onLoad', function() {
71+
const lazyLoadImage = mount(
72+
<LazyLoadImage effect="blur" />
73+
);
74+
75+
const img = findRenderedDOMComponentWithTag(lazyLoadImage.instance(), 'img');
76+
77+
Simulate.load(img);
78+
79+
const loadedWrapper = scryRenderedDOMComponentsWithClass(lazyLoadImage.instance(), 'lazy-load-image-loaded');
80+
81+
expect(loadedWrapper.length).toEqual(1);
82+
});
83+
84+
it('adds the effect class', function() {
85+
const lazyLoadImage = mount(
86+
<LazyLoadImage effect="blur" />
87+
);
88+
89+
const blurSpan = scryRenderedDOMComponentsWithClass(lazyLoadImage.instance(), 'blur');
90+
91+
expect(blurSpan.length).toEqual(1);
92+
});
93+
6994
it('doesn\'t render placeholder background when not defined', function() {
7095
const lazyLoadImage = mount(
7196
<LazyLoadImage />

src/effects/black-and-white.css

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.lazy-load-image-background.black-and-white {
2+
filter: grayscale(1);
3+
}
4+
5+
.lazy-load-image-background.black-and-white.lazy-load-image-loaded {
6+
filter: grayscale(0);
7+
transition: filter .3s;
8+
}
9+
10+
.lazy-load-image-background.black-and-white > img {
11+
opacity: 0;
12+
}
13+
14+
.lazy-load-image-background.black-and-white.lazy-load-image-loaded > img {
15+
opacity: 1;
16+
transition: opacity .3s;
17+
}

src/effects/blur.css

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.lazy-load-image-background.blur {
2+
filter: blur(15px);
3+
}
4+
5+
.lazy-load-image-background.blur.lazy-load-image-loaded {
6+
filter: blur(0);
7+
transition: filter .3s;
8+
}
9+
10+
.lazy-load-image-background.blur > img {
11+
opacity: 0;
12+
}
13+
14+
.lazy-load-image-background.blur.lazy-load-image-loaded > img {
15+
opacity: 1;
16+
transition: opacity .3s;
17+
}

src/effects/opacity.css

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.lazy-load-image-background.opacity {
2+
background-image: none !important;
3+
opacity: 0;
4+
}
5+
6+
.lazy-load-image-background.opacity.lazy-load-image-loaded {
7+
opacity: 1;
8+
transition: opacity .3s;
9+
}

webpack.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ module.exports = {
2626
presets: ['env']
2727
}
2828
}
29+
}, {
30+
test: /\.css$/,
31+
loaders: ['style-loader', 'css-loader'],
32+
exclude: /node_modules/
2933
}
3034
]
3135
},

0 commit comments

Comments
 (0)