diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
new file mode 100644
index 000000000..af2870517
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class ImageLoader extends React.PureComponent {
+
+ static propTypes = {
+ src: PropTypes.string.isRequired,
+ }
+
+ state = {
+ loading: true,
+ error: false,
+ }
+
+ componentWillMount() {
+ this.loadImage(this.props.src);
+ }
+
+ componentWillReceiveProps(props) {
+ this.loadImage(props.src);
+ }
+
+ loadImage(src) {
+ const image = new Image();
+ image.onerror = () => this.setState({loading: false, error: true});
+ image.onload = () => this.setState({loading: false, error: false});
+ image.src = src;
+ this.lastSrc = src;
+ this.setState({loading: true});
+ }
+
+ render() {
+ const { src } = this.props;
+ const { loading, error } = this.state;
+
+ // TODO: handle image error state
+
+ const imageClass = `image-loader__img ${loading ? 'image-loader__img-loading' : ''}`;
+
+ return ; // eslint-disable-line jsx-a11y/img-has-alt
+ }
+
+}
+
+export default ImageLoader;
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index a8912841b..effa0aea3 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -3,10 +3,10 @@ import LoadingIndicator from '../../../components/loading_indicator';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ExtendedVideoPlayer from '../../../components/extended_video_player';
-import ImageLoader from 'react-imageloader';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImageLoader from './image_loader';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -73,7 +73,7 @@ class MediaModal extends ImmutablePureComponent {
}
if (attachment.get('type') === 'image') {
- content = ;
+ content = ;
} else if (attachment.get('type') === 'gifv') {
content = ;
}
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index 4afbd12cf..e396f04cc 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -1137,13 +1137,13 @@
}
}
-.transparent-background,
-.imageloader {
- background: url('../images/void.png');
+.image-loader__img {
+ transition: opacity 0.3s linear;
+ opacity: 1;
}
-.imageloader {
- display: block;
+.image-loader__img-loading {
+ opacity: 0.7;
}
.navigation-bar {
@@ -2852,6 +2852,11 @@ button.icon-button.active i.fa-retweet {
max-width: 80vw;
max-height: 80vh;
}
+
+ img {
+ display: block;
+ background: url('../images/void.png') repeat;
+ }
}
.media-modal__close {
diff --git a/package.json b/package.json
index 92221d8e4..c686e99e7 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,6 @@
"react-addons-perf": "^15.4.2",
"react-addons-shallow-compare": "^15.5.2",
"react-dom": "^15.5.4",
- "react-imageloader": "^2.1.0",
"react-immutable-proptypes": "^2.1.0",
"react-immutable-pure-component": "^0.0.4",
"react-intl": "^2.3.0",
diff --git a/yarn.lock b/yarn.lock
index d3317f59c..5bef1e0b9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5436,10 +5436,6 @@ react-fuzzy@^0.3.3:
classnames "^2.2.3"
fuse.js "^2.2.0"
-react-imageloader@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/react-imageloader/-/react-imageloader-2.1.0.tgz#a58401970b3282386aeb810c43175165634f6308"
-
react-immutable-proptypes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"