Photo.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import React from 'react';
  2. import styled from 'styled-components';
  3. import throttle from 'lodash.throttle';
  4. import Spinner from './components/Spinner';
  5. import getSuitableImageSize from './utils/getSuitableImageSize';
  6. interface IPhotoProps extends React.HTMLAttributes<any> {
  7. src: string;
  8. onPhotoResize: () => void;
  9. loadingElement?: JSX.Element;
  10. brokenElement?: JSX.Element;
  11. }
  12. type PhotoState = {
  13. loaded: boolean;
  14. broken: boolean;
  15. naturalWidth: number;
  16. naturalHeight: number;
  17. width: number;
  18. height: number;
  19. };
  20. const PhotoImage = styled.img`
  21. will-change: transform;
  22. cursor: -webkit-grab;
  23. &:active {
  24. cursor: -webkit-grabbing;
  25. }
  26. `;
  27. export default class Photo extends React.Component<IPhotoProps, PhotoState> {
  28. static displayName = 'Photo';
  29. readonly state = {
  30. loaded: false,
  31. broken: false,
  32. naturalWidth: 1,
  33. naturalHeight: 1,
  34. width: 1,
  35. height: 1,
  36. };
  37. private isMount = true;
  38. constructor(props) {
  39. super(props);
  40. this.handleResize = throttle(this.handleResize, 8);
  41. }
  42. componentDidMount() {
  43. const currPhoto = new Image();
  44. currPhoto.src = this.props.src;
  45. currPhoto.onload = this.handleImageLoaded;
  46. currPhoto.onerror = this.handleImageBroken;
  47. window.addEventListener('resize', this.handleResize);
  48. }
  49. componentWillUnmount() {
  50. this.isMount = false;
  51. window.removeEventListener('resize', this.handleResize);
  52. }
  53. handleImageLoaded = e => {
  54. const { naturalWidth, naturalHeight } = e.target;
  55. if (this.isMount) {
  56. this.setState({
  57. loaded: true,
  58. naturalWidth,
  59. naturalHeight,
  60. ...getSuitableImageSize(naturalWidth, naturalHeight),
  61. });
  62. }
  63. }
  64. handleImageBroken = () => {
  65. if (this.isMount) {
  66. this.setState({
  67. broken: true,
  68. });
  69. }
  70. }
  71. handleResize = () => {
  72. const { loaded, naturalWidth, naturalHeight } = this.state;
  73. if (loaded && this.isMount) {
  74. this.setState(
  75. getSuitableImageSize(naturalWidth, naturalHeight),
  76. this.props.onPhotoResize,
  77. );
  78. }
  79. }
  80. render() {
  81. const {
  82. src,
  83. loadingElement,
  84. brokenElement,
  85. ...restProps
  86. } = this.props;
  87. const { loaded, broken, width, height } = this.state;
  88. if (src && !broken) {
  89. if (loaded) {
  90. return (
  91. <PhotoImage
  92. src={src}
  93. width={width}
  94. height={height}
  95. {...restProps}
  96. />
  97. );
  98. }
  99. return loadingElement || <Spinner />;
  100. }
  101. return brokenElement || null;
  102. }
  103. }