index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react'
  2. import classnames from 'classnames'
  3. import styles from './Avatar.less'
  4. const logo = require('assets/images/profile.png')
  5. import { Modal, Spin } from 'antd'
  6. import { IAvatarProps, TSize, TImageState } from './types'
  7. import { useIntersectionObserver } from './useIntersectionObserver'
  8. export const Avatar: React.FC<IAvatarProps> = ({
  9. path,
  10. size,
  11. enlarge,
  12. border
  13. }) => {
  14. const elementRef = useRef(null)
  15. const [formVisible, setFormVisible] = useState(false)
  16. const [inView, entry] = useIntersectionObserver(elementRef, {
  17. threshold: 0,
  18. rootMargin: '0%',
  19. root: null
  20. })
  21. const [status, setStatus] = useState<TImageState>('loading')
  22. const loading = useMemo(() => {
  23. return size === 'profile' ? (
  24. <Spin size="large" />
  25. ) : (
  26. <Spin size={size as TSize} />
  27. )
  28. }, [size, status])
  29. const showEnlarge = useCallback(() => {
  30. setFormVisible(true)
  31. }, [formVisible])
  32. const hideEnlarge = useCallback(() => {
  33. setFormVisible(false)
  34. }, [formVisible])
  35. const itemClass = useMemo(() => {
  36. return classnames({
  37. [styles.isEnlarge]: enlarge,
  38. [styles.large]: size === 'large',
  39. [styles.small]: size === 'small',
  40. [styles.profile]: size === 'profile',
  41. [styles.default]: size === 'default',
  42. [styles.height0]: status === 'loading',
  43. [styles.height1]: status !== 'loading'
  44. })
  45. }, [size, enlarge, status])
  46. const imgContent = useMemo(() => {
  47. const img = enlarge ? (
  48. <img className={itemClass} ref={elementRef} onClick={showEnlarge} />
  49. ) : (
  50. <img className={itemClass} ref={elementRef} />
  51. )
  52. return (
  53. <>
  54. {status === 'loading' ? loading : ''}
  55. {img}
  56. </>
  57. )
  58. }, [enlarge, showEnlarge, status])
  59. const wrapper = useMemo(() => {
  60. return classnames({
  61. [styles.profileWrapper]: size === 'profile',
  62. [styles.defaultWrapper]: size === 'default',
  63. [styles.smallWrapper]: size === 'small',
  64. [styles.largeWrapper]: size === 'large',
  65. [styles.border]: border
  66. })
  67. }, [size])
  68. const loaded = useCallback(() => {
  69. if (logo !== elementRef.current.src) {
  70. setStatus('loaded')
  71. }
  72. }, [status])
  73. const loadFail = useCallback(() => {
  74. elementRef.current.src = logo
  75. setStatus('loadFail')
  76. }, [status])
  77. useEffect(() => {
  78. if (!inView) {
  79. return
  80. }
  81. if (!elementRef.current.src || elementRef.current.src !== path) {
  82. elementRef.current.src = path
  83. }
  84. elementRef.current.addEventListener('load', loaded, false)
  85. elementRef.current.addEventListener('error', loadFail, false)
  86. return () => {
  87. elementRef.current.removeEventListener('load', loaded)
  88. elementRef.current.removeEventListener('error', loadFail)
  89. }
  90. }, [inView, status, path])
  91. const modalSrc = useMemo(() => {
  92. return status === 'loaded' ? path : logo
  93. }, [path, status])
  94. return (
  95. <div className={wrapper}>
  96. {imgContent}
  97. <Modal
  98. title={null}
  99. footer={null}
  100. visible={formVisible}
  101. onCancel={hideEnlarge}
  102. >
  103. <img src={modalSrc} className={styles.sourceSrc} />
  104. </Modal>
  105. </div>
  106. )
  107. }
  108. export default Avatar