about summary refs log tree commit diff
path: root/app/javascript/mastodon/features/ui/components/image_loader.js
diff options
context:
space:
mode:
authorYamagishi Kazutoshi <ykzts@desire.sh>2017-06-27 20:43:53 +0900
committerEugen Rochko <eugen@zeonfederated.com>2017-06-27 13:43:53 +0200
commit8f2c91568c7ab552a87d02813e6b02be65f8707f (patch)
treeaecba410cbd7be87629037e022b3cb4ba51e9827 /app/javascript/mastodon/features/ui/components/image_loader.js
parent98eaa2aa27100ac0037cced9d4d4c86bcb9396e7 (diff)
Maintain aspect ratio for preview image (#3966)
Diffstat (limited to 'app/javascript/mastodon/features/ui/components/image_loader.js')
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js116
1 files changed, 96 insertions, 20 deletions
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
index 5c3879970..52c3a898b 100644
--- a/app/javascript/mastodon/features/ui/components/image_loader.js
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 
 export default class ImageLoader extends React.PureComponent {
 
@@ -20,46 +21,121 @@ export default class ImageLoader extends React.PureComponent {
     error: false,
   }
 
-  componentWillMount() {
-    this._loadImage(this.props.src);
+  removers = [];
+
+  get canvasContext() {
+    if (!this.canvas) {
+      return null;
+    }
+    this._canvasContext = this._canvasContext || this.canvas.getContext('2d');
+    return this._canvasContext;
+  }
+
+  componentDidMount () {
+    this.loadImage(this.props);
+  }
+
+  componentWillReceiveProps (nextProps) {
+    if (this.props.src !== nextProps.src) {
+      this.loadImage(nextProps);
+    }
   }
 
-  componentWillReceiveProps(props) {
-    this._loadImage(props.src);
+  loadImage (props) {
+    this.removeEventListeners();
+    this.setState({ loading: true, error: false });
+    Promise.all([
+      this.loadPreviewCanvas(props),
+      this.loadOriginalImage(props),
+    ])
+      .then(() => {
+        this.setState({ loading: false, error: false });
+        this.clearPreviewCanvas();
+      })
+      .catch(() => this.setState({ loading: false, error: true }));
   }
 
-  _loadImage(src) {
+  loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => {
     const image = new Image();
+    const removeEventListeners = () => {
+      image.removeEventListener('error', handleError);
+      image.removeEventListener('load', handleLoad);
+    };
+    const handleError = () => {
+      removeEventListeners();
+      reject();
+    };
+    const handleLoad = () => {
+      removeEventListeners();
+      this.canvasContext.drawImage(image, 0, 0, width, height);
+      resolve();
+    };
+    image.addEventListener('error', handleError);
+    image.addEventListener('load', handleLoad);
+    image.src = previewSrc;
+    this.removers.push(removeEventListeners);
+  })
 
-    image.onerror = () => this.setState({ loading: false, error: true });
-    image.onload  = () => this.setState({ loading: false, error: false });
+  clearPreviewCanvas () {
+    const { width, height } = this.canvas;
+    this.canvasContext.clearRect(0, 0, width, height);
+  }
 
+  loadOriginalImage = ({ src }) => new Promise((resolve, reject) => {
+    const image = new Image();
+    const removeEventListeners = () => {
+      image.removeEventListener('error', handleError);
+      image.removeEventListener('load', handleLoad);
+    };
+    const handleError = () => {
+      removeEventListeners();
+      reject();
+    };
+    const handleLoad = () => {
+      removeEventListeners();
+      resolve();
+    };
+    image.addEventListener('error', handleError);
+    image.addEventListener('load', handleLoad);
     image.src = src;
+    this.removers.push(removeEventListeners);
+  });
 
-    this.setState({ loading: true });
+  removeEventListeners () {
+    this.removers.forEach(listeners => listeners());
+    this.removers = [];
   }
 
-  render() {
-    const { alt, src, previewSrc, width, height } = this.props;
+  setCanvasRef = c => {
+    this.canvas = c;
+  }
+
+  render () {
+    const { alt, src, width, height } = this.props;
     const { loading } = this.state;
 
+    const className = classNames('image-loader', {
+      'image-loader--loading': loading,
+    });
+
     return (
-      <div className='image-loader'>
-        <img
-          alt={alt}
-          className='image-loader__img'
-          src={src}
+      <div className={className}>
+        <canvas
+          className='image-loader__preview-canvas'
           width={width}
           height={height}
+          ref={this.setCanvasRef}
         />
 
-        {loading &&
+        {!loading && (
           <img
-            alt=''
-            src={previewSrc}
-            className='image-loader__preview-img'
+            alt={alt}
+            className='image-loader__img'
+            src={src}
+            width={width}
+            height={height}
           />
-        }
+        )}
       </div>
     );
   }