commit: 8f2c91568c7ab552a87d02813e6b02be65f8707f
parent: 98eaa2aa27100ac0037cced9d4d4c86bcb9396e7
Author: Yamagishi Kazutoshi <ykzts@desire.sh>
Date: Tue, 27 Jun 2017 20:43:53 +0900
Maintain aspect ratio for preview image (#3966)
Diffstat:
2 files changed, 113 insertions(+), 33 deletions(-)
diff --git 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>
);
}
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
@@ -1099,20 +1099,22 @@
.image-loader {
position: relative;
-}
-.image-loader__preview-img {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- filter: blur(2px);
-}
+ &.image-loader--loading {
+ .image-loader__preview-canvas {
+ filter: blur(2px);
+ }
+ }
-.media-modal img.image-loader__preview-img {
- width: 100%;
- height: 100%;
+ .image-loader__img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ background-image: none;
+ }
}
.navigation-bar {
@@ -2933,6 +2935,7 @@ button.icon-button.active i.fa-retweet {
position: relative;
img,
+ canvas,
video {
max-width: 80vw;
max-height: 80vh;
@@ -2940,7 +2943,8 @@ button.icon-button.active i.fa-retweet {
height: auto;
}
- img {
+ img,
+ canvas {
display: block;
background: url('../images/void.png') repeat;
}