about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js45
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js4
-rw-r--r--app/javascript/styles/components.scss15
3 files changed, 57 insertions, 7 deletions
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 <img className={imageClass} src={src} />; // 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 = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
+      content = <ImageLoader src={url} />;
     } else if (attachment.get('type') === 'gifv') {
       content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
     }
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 {